[英]How does C++ Nifty Counter idiom guarantee both static initialization and static deinitialization
在学习 isocpp 时,我遇到了一个FAQ 。 其中说:“什么是保证 static 初始化和 static 取消初始化的技术?” 简短的回答只是暗示:
简短的回答:使用 Nifty Counter Idiom(但请确保您了解重要的权衡。)。
到目前为止,我无法理解Nifty Counter Idiom是什么意思以及它如何解决 Fiasco 问题。 I am already aware of Construct On First Use Idiom , and its side effects while using a static object or a static pointer (wrapped in a global function returning reference)
这是一个实际上容易出现 Fiasco 问题的简单示例:
//x.cpp
include "x.hpp" // struct X { X(int); void f(); };
X x{ 10 };
struct Y { Y(); };
Y::Y(){ x.f(); };
//y.cpp
include <iostream>
include "y.hpp" // struct Y { Y(); };
struct X { X(int); void f(); private: int _x; };
X::X(int i) { _x = i; };
void X::f() { std::cout << _x << std::endl; };
Y y;
从这里,它提到了 Nifty Counter Idiom 的主要用途
意图:确保非本地 static object 在首次使用前被初始化,并且仅在最后一次使用 object 后被销毁。
现在我现在需要的是, Nifty Counter Idiom如何具体解决上述代码中的 static 订单初始化和取消初始化,而不管其他解决方法为constinit
。 鉴于上述程序是这样编译的:
~$ g++ x.cpp y.cpp &&./a.out
>> 10
~$ g++ y.cpp x.cpp &&./a.out
>> 0
嗯,这是一个漂亮的计数器机制。 它不会“修复”订购惨败; 相反,它正在做的是使用 integer 计数器来制作它,以便编译器执行其静态初始化和静态销毁的(仍然未定义的)顺序无关紧要。
它是如何做到的? 简单的; 无论哪个StreamInitializer
对象的静态初始化首先发生, StreamInitializer()
构造函数要做的第一件事就是递增nifty_counter
。 由于nifty_counter
被默认初始化为零,这意味着构造函数中的if (nifty_counter++ == 0)
测试将仅返回一次 true,即第一次执行时,无论从哪个 .cpp 文件触发初始化。 这将导致Stream
object 的放置新初始化仅发生一次,这是第一个StreamInitializer
初始化的副作用。
同样,每个~StreamInitializer()
析构函数都会减少nifty_count
,因为~StreamInitializer()
析构函数调用的数量(在程序执行结束时)与StreamInitializer()
构造函数调用的数量完全相同(在开始执行程序),我们保证这将是对~StreamInitializer()
的最后一次调用, nifty_counter
减回零。 无论编译器选择破坏发生的顺序如何,这都是正确的,这意味着Stream
object 的破坏将仅在最后一次~StreamInitializer()
调用期间发生,这就是您想要的。
The upshot is that as long as your.cpp file declares a StreamInitializer
object first, it can safely access the Stream
object, because the presence of a valid StreamInitializer
object (and in particular, the execution of its constructor and destructor at the appropriate times)保证Stream
object 在 that.cpp 文件中的任何后续静态对象构造函数/析构函数代码中都是有效的。
我认为您提供的链接很好地解释了它。 但这是我的尝试。
// 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 的 static 对象的任何初始化顺序。 stream
和x
在不同的 TU 中,所以可以先初始化stream
,这样就可以了。 或者x
可以在stream
之前初始化,这很糟糕,因为x
的初始化使用了尚未初始化的stream
。
如果stream
在x
之前被破坏,析构函数也会出现同样的问题。
它利用了在 TU 中对象按顺序初始化和销毁的事实。 但是我们不能为每个 TU 使用stream
object,所以我们使用了一个技巧,我们改为为每个 TU 使用StreamInitializer
object。 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 ();
}
我们的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
object。 只是引用绑定到仍然不存在的 object,它位于stream_buf
memory 中。 这个 object 稍后会构建。
每个编译单元都有一个streamInitializer
object。 这些对象以任何顺序初始化,但这并不重要,因为其中只有一个nifty_counter
将为0
并且仅在该行上new (&stream) Stream ();
被执行。 此行实际上在 memory 位置 stream_buf 处创建stream
stream_buf
。
现在为什么行X x{};
在这种情况下好吗? 因为这个 TU 的streamInitializer
object 在x
之前初始化,这就保证了stream
在x
之前初始化。 对于在使用stream
object 之前正确包含stream.hpp
的每个 TU 而言,都是如此。
相同的逻辑适用于析构函数,但顺序相反。
不同TU中的Static对象可以按任意顺序初始化。 如果一个 TU 的一个 static object 在其构造函数或析构函数中使用另一个 static26696CFDE63131ZCBBEB6
在 TU object 中按顺序构造和销毁。
在漂亮的 connter 成语中:
stream
是对该 memory 位置的 object 的引用streamInitializer
object 的 TU 中都会创建一个stream
object。streamInitializer
将创建 stream object,第一个被初始化x
之前的 TU 中,至少该 TU 中的streamInitializer
已经初始化。 这意味着至少构造了一个streamInitializer
,这意味着stream
是在x
的构造函数之前构造的。包括为简洁而省略的警卫
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.