Consider the following, where we have two file scoped objects in different translation units, which is the usual setup for undefined-behavior by way of the initialization order fiasco:
a.hpp:
struct thing {
public:
thing(int value);
~thing();
int value() const;
static int count();
private:
int _value;
};
a.cpp:
#include "a.hpp"
#include <atomic>
namespace {
std::atomic<int> things;
}
thing::thing(int value) : _value(value) {
++things;
}
thing::~thing() {
--things;
}
int thing::value() const {
return _value;
}
int thing::count() {
return things.load();
}
b.cpp:
#include <iostream>
#include "a.hpp"
namespace {
thing static_thing(42);
}
void foo() {
std::cout << static_thing.value() << ' ' << thing::count() << '\n';
}
Is this code subject to an initialization order fiasco between the file scoped atomic things
in a.cpp and the file scoped static_thing
in b.cpp? If not, why not? In particular, what is special about std::atomic that removes what would otherwise be a clear init order fiasco? Is there a particular concept that can be named to enforce this with a static assert? Something like:
static_assert(std::is_trivial<decltype(things)>::value, "file static counter is not trivial");
If not std::is_trivial
, is there another concept and associated type trait that better models this?
Conversely, is there a de-initialization fiasco? Same questions about if so, why, or why not.
std::atomic<>
is a standard layout type with trivial default constructors, and trivial destructors. Therefore, it is initialized at static initialization phase, before the dynamic initialization phase when constructors of global objects get called.
In other words, no initialization order fiasco happens here.
Since you do not explicitly initialize the namespace scoped std::atomic<int>
, it is going to be zero-initialized.
§ 3.6.2 Initialization of non-local variables
Variables with static storage duration (3.7.1) or thread storage duration (3.7.2) shall be zero-initialized (8.5) before any other initialization takes place.
Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place.
My understanding of the C++ "initialisation order fiasco" is that it only applies when constructors need to be called at runtime. If the code can devolve down into the initialisation of a memory location to a fixed value, then that value gets put in the "initialised data" linker section (.data
) like every other pre-initialised POD (Plain Ol' Data), and there is no fiasco.
I'd suggest that an atomic
meets that very criterion.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With