Does value initialization work for atomic objects?

2019-04-07 01:21发布

By work here, I take to mean that std::atomic<T> a{} effectively zero initializes a. I have always been thinking so and have been practically using it until this. Before explaining my understanding of this, I want to show that, at the very least, gcc and clang are doing it in practice.

#include <cstring>
#include <atomic>
#include <iostream>

int main() {
  using atomic = std::atomic<int>;  
  auto p = (atomic*)operator new(sizeof(atomic));
  std::memset(p, -1, sizeof(atomic));
  new(p) atomic{};
  std::cout << p->load() << std::endl;
}

The output is 0 on both gcc and clang.

Following is my explanation of why this should work (you may think otherwise, of course). The standard says that

In the following operation definitions:

  • an A refers to one of the atomic types.

[...]

A::A() noexcept = default;

Effects: leaves the atomic object in an uninitialized state. [ Note: These semantics ensure compatibility with C. — end note ]

It basically says that the default constructor is trivial and does nothing. I'm OK with this, but I don't see how this makes value initialization non-applicable. According to cppref, the effects of value initialization include (emphasis mine):

if T is a class type with a default constructor that is neither user-provided nor deleted (that is, it may be a class with an implicitly-defined or defaulted default constructor), the object is zero-initialized and then it is default-initialized if it has a non-trivial default constructor;

std::atomic has a defaulted default constructor, so the object is

  1. zero-initialized and then
  2. it is default-initialized if it has a non-trivial default constructor.

Point 2 doesn't apply here since the defaulted default constructor is trivial, but I don't see any statement that renders point 1 in-effective. Is my understanding correct or am I missing something?

1条回答
在下西门庆
2楼-- · 2019-04-07 01:38

Ultimately the crux of the matter for the value initialization case is in [dcl.init]/7, bullets 1 and 2:

To value-initialize an object of type T means:

  • if T is a (possibly cv-qualified) class type (Clause [class]) with a user-provided constructor ([class.ctor]), then the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
  • T is a (possibly cv-qualified) non-union class type without a user-provided constructor, then the object is zero-initialized and, if T's implicitly-declared default constructor is non-trivial, that constructor is called.
  • ...

Which of the two bullets above is applied depends on the c'tor being user-provided. What I didn't remember in the comments to the other answer, is the intricacies of = default; when applied to that. If we look at the definition given at [dcl.fct.def.default]/4 (emphasis mine):

Explicitly-defaulted functions and implicitly-declared functions are collectively called defaulted functions, and the implementation shall provide implicit definitions for them ([class.ctor] [class.dtor], [class.copy]), which might mean defining them as deleted. A special member function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed. [ Note: Declaring a function as defaulted after its first declaration can provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base. — end note ]

We see that the default c'tor of atomic is not user provided, because it is declared as defaulted, as opposed to being declared and then defined as defaulted. So the second bullet of [dcl.init]/7 is applicable, the object is zero-initialized, followed by the (non-)invocation of the (trivial default) constructor which does nothing.

查看更多
登录 后发表回答