I think the link you provided explains it pretty well. But here's my attempt at it.
First what is the problem
// stream.hpp
struct Stream {
Stream ();
~Stream ();
void operator<<(int);
};
extern Stream stream; // global stream object
// stream.cpp
#include "stream.hpp"
Stream::Stream () { /* initialize things */ }
Stream::~Stream () { /* clean up */ }
void Stream::operator<<(int a) { /* write to stream */ }
Stream stream{};
// app.cpp
#include "stream.hpp"
struct X
{
X()
{
stream << 24; // use stream object
}
~X()
{
stream << 1024; // use stream object
}
};
X x{}; // in this static initialization the static stream object is used
The C++ standard does not guarantee any order of initialization of static objects across TUs. stream and x are in different TUs, so stream could be initialized first, which is fine. Or x could be initialized before stream which is bad because the initialization of x uses stream which is not initialized yet.
The same problem happens for destructors if stream is destructed before x.
The nifty counter solution
It leverages the fact that in a TU objects are initialized and destructed in order. But we can't have a stream object for every TU, so we use a trick, we instead use a StreamInitializer object for every TU. This streamInitializer will be constructed before x and destructed after x and we make sure that only the first streamInitializer that gets constructed creates the stream object and only the last streamInitializer to be destructed destroys the stream object:
// stream.hpp
struct Stream {
Stream ();
~Stream ();
void operator<<(int);
};
extern Stream& stream; // global stream object
struct StreamInitializer {
StreamInitializer ();
~StreamInitializer ();
};
static StreamInitializer streamInitializer{}; // static initializer for every TU
// stream.cpp
#include "stream.hpp"
static int nifty_counter{}; // zero initialized at load time
static typename std::aligned_storage<sizeof (Stream), alignof (Stream)>::type
stream_buf; // memory for the stream object
Stream& stream = reinterpret_cast<Stream&> (stream_buf);
Stream::Stream () { // initialize things }
Stream::~Stream () { // clean-up }
StreamInitializer::StreamInitializer ()
{
if (nifty_counter++ == 0) new (&stream) Stream (); // placement new
}
StreamInitializer::~StreamInitializer ()
{
if (--nifty_counter == 0) (&stream)->~Stream ();
}
Our app.cpp is the same
// app.cpp
#include "stream.hpp"
struct X
{
X()
{
stream << 24; // use stream object
}
~X()
{
stream << 1024; // use stream object
}
};
X x{}; // in this static initialization the static stream object is used
Now stream is not a value, it's a reference. So in this line Stream& stream = reinterpret_cast<Stream&> (stream_buf); no Stream object is created. Just the reference is bound to the still non existing object residing at the stream_buf memory. This object will be constructed later.
Every compilation unit has a streamInitializer object. Those objects initialize in any order, but it doesn't matter because on only one of them nifty_counter will be 0 and on that and only on that one the line new (&stream) Stream (); is executed. This line actually creates the stream object at the memory location stream_buf.
Now why is the line X x{}; ok in this scenario? Because this TU's streamInitializer object is initialized before x, which ensures that stream is initialized before x. This is true for every TU where stream.hpp is correctly included before any use of the stream object.
The same logic applies to the destructor, but in reverse order.
Recap
Static objects in different TU can be initialized in any order. This is a problem if one static object from one TU uses in it's constructor or destructor another static object from another TU.
In a TU object are constructed and destructed in order.
In the nifty connter idiom:
- one TU holds a raw memory for the stream object
stream is a reference to the object at that memory location
- a
streamInitializer object is created in every every TU that uses the stream object.
- only 1
streamInitializer will create the stream object, the 1st that is initialized
- in a TU before every use of
x at least the streamInitializer in that TU is already initialized. This means that at least one streamInitializer was constructed, this means that stream is constructed before the constructor of x.
include guards omitted for brevity