簡體   English   中英

C++ Nifty Counter 成語如何保證 static 初始化和 static 去初始化

[英]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 對象的任何初始化順序。 streamx在不同的 TU 中,所以可以先初始化stream ,這樣就可以了。 或者x可以在stream之前初始化,這很糟糕,因為x的初始化使用了尚未初始化的stream

如果streamx之前被破壞,析構函數也會出現同樣的問題。

漂亮的計數器解決方案

它利用了在 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之前初始化,這就保證了streamx之前初始化。 對於在使用stream object 之前正確包含stream.hpp的每個 TU 而言,都是如此。

相同的邏輯適用於析構函數,但順序相反。

回顧

不同TU中的Static對象可以按任意順序初始化。 如果一個 TU 的一個 static object 在其構造函數或析構函數中使用另一個 static26696CFDE63131ZCBBEB6

在 TU object 中按順序構造和銷毀。

在漂亮的 connter 成語中:

  • 一個 TU 持有原始 memory 用於 stream object
  • stream是對該 memory 位置的 object 的引用
  • 在每個使用streamInitializer object 的 TU 中都會創建一個stream object。
  • 只有 1 個streamInitializer將創建 stream object,第一個被初始化
  • 在每次使用x之前的 TU 中,至少該 TU 中的streamInitializer已經初始化。 這意味着至少構造了一個streamInitializer ,這意味着stream是在x的構造函數之前構造的。

包括為簡潔而省略的警衛

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM