我认为您提供的链接很好地解释了它。但这是我的尝试。
首先是什么问题
// 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
C++ 标准不保证跨 TU 的静态对象的任何初始化顺序。stream
并且x
在不同的 TU 中,所以stream
可以先初始化,这很好。或者x
可能在stream
这之前被初始化是不好的,因为x
使用stream
的初始化尚未初始化。
如果stream
在之前被破坏,析构函数也会出现同样的问题x
。
漂亮的计数器解决方案
它利用了在 TU 中对象按顺序初始化和销毁的事实。但是我们不能stream
每个 TU 都有一个对象,所以我们使用了一个技巧,而是StreamInitializer
为每个 TU 使用一个对象。这streamInitializer
将在之前构造x
并在之后销毁x
,我们确保只有第一个streamInitializer
被构造的创建流对象,只有最后一个streamInitializer
被破坏的才会破坏流对象:
// 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 ();
}
我们的app.cpp是一样的
// 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
现在stream
不是一个值,它是一个参考。所以在这一行中Stream& stream = reinterpret_cast<Stream&> (stream_buf);
没有Stream
创建任何对象。stream_buf
只是引用绑定到内存中仍然不存在的对象。该对象将在稍后构建。
每个编译单元都有一个streamInitializer
对象。这些对象以任何顺序初始化,但这并不重要,因为只有其中一个nifty_counter
将是0
并且在那个上并且只有在那个上new (&stream) Stream ();
才会执行该行。这一行实际上stream
在内存位置创建了对象stream_buf
。
现在为什么X x{};
在这种情况下线路正常?因为这个TU的streamInitializer
对象是在之前初始化的x
,这就保证了stream
在之前的初始化x
。stream.hpp
这对于在使用stream
对象之前正确包含的每个 TU 都是如此。
相同的逻辑适用于析构函数,但顺序相反。
回顾
不同 TU 中的静态对象可以按任意顺序初始化。如果来自一个 TU 的一个静态对象在它的构造函数或析构函数中使用来自另一个 TU 的另一个静态对象,则会出现问题。
在 TU 中,对象按顺序构造和销毁。
在漂亮的 connter 成语中:
- 一个 TU 持有流对象的原始内存
stream
是对该内存位置的对象的引用
streamInitializer
在每个使用该对象的 TU 中都会创建一个对象stream
。
- 只有 1
streamInitializer
会创建流对象,第一个被初始化
- 在每次使用之前在一个 TU 中
x
,至少该streamInitializer
TU 已经初始化。这意味着至少streamInitializer
构造了一个,这意味着在stream
构造函数之前构造了x
.
包括为简洁而省略的警卫