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
- zero-initialized and then
- 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?
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.