[英]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.