8
votes

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
In the question you link to, the atomic variable is not explicitly initialized, so the default constructor is used. And it does nothing. But you explicitly initialize your variable, invoking the value initialization machinery that you describe, so you're good.Cheers and hth. - Alf
All that stuff with placement new etc. is just distraction.Cheers and hth. - Alf
Alright, you made me go verify the assumptions I made in the comments to the other post. So I apologize for that. You are correct.StoryTeller - Unslander Monica
@Cheersandhth.-Alf The placement new is needed to show that the zero is indeed a result of value initialization, not some junk value happen to be there.Lingxi
The issue is that std::atomic<T> under the present wording is not required to contain a T. You can zero the bytes in the atomic but that's not enough to reason about the T value it refers to. This is obviously a suboptimal state of affairs; there's a paper in flight to change this.T.C.

1 Answers

6
votes

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.