跳轉到內容

更多 C++ 習語/巧妙計數器

來自華夏公益教科書,開放的書籍,為開放的世界

巧妙計數器

[編輯 | 編輯原始碼]

確保非區域性靜態物件在首次使用之前初始化,並且僅在最後使用該物件後銷燬。

也稱為

[編輯 | 編輯原始碼]

Schwarz 計數器

當靜態物件使用其他靜態物件時,初始化問題變得更加複雜。如果靜態物件具有非平凡的初始化,則必須在使用之前對其進行初始化。跨編譯單元的靜態物件的初始化順序沒有明確定義。多個靜態物件(分佈在多個編譯單元中)可能正在使用單個靜態物件。因此,必須在使用之前對其進行初始化。一個例子是std::cout,它通常被其他幾個靜態物件使用。

解決方案和示例程式碼

[編輯 | 編輯原始碼]

“巧妙計數器”或“Schwarz 計數器”習語是將引用計數習語應用於靜態物件初始化的一個例子。

// Stream.h
#ifndef STREAM_H
#define STREAM_H

struct Stream {
  Stream ();
  ~Stream ();
};
extern Stream& stream; // global stream object

static struct StreamInitializer {
  StreamInitializer ();
  ~StreamInitializer ();
} streamInitializer; // static initializer for every translation unit

#endif // STREAM_H
// Stream.cpp
#include "Stream.h"

#include <new>         // placement new
#include <type_traits> // aligned_storage

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 ();
}

Stream 類的標頭檔案必須在對Stream 物件呼叫任何成員函式之前包含。在每個編譯單元中都包含了StreamInitializer 類的例項。對Stream 物件的任何使用都在包含標頭檔案之後,這確保了在使用Stream 物件之前呼叫了初始化器物件的建構函式。

Stream 類的標頭檔案聲明瞭對Stream 物件的引用。此外,該引用是extern,這意味著它在一個翻譯單元中定義,並且對它的訪問是由連結器而不是編譯器解析的。
Stream 類的實現檔案最終定義了Stream 物件,但使用了一種不尋常的方式:它首先定義了一個靜態(即特定於翻譯單元)緩衝區。該緩衝區既正確對齊又足夠大以儲存型別為Stream 的物件。然後,將標頭檔案中定義的Stream 物件的引用設定為指向該緩衝區。
這種緩衝區變通方法使我們可以精細地控制何時呼叫Stream 物件的建構函式和解構函式。在上面的示例中,建構函式是在第一個StreamInitializer 物件的建構函式中呼叫的,使用 placement new 將其放置在緩衝區中。Stream 物件的解構函式是在最後一個StreamInitializer 物件被銷燬時呼叫的。

這種變通方法是必要的,因為在 Stream.cpp 中定義一個Stream 變數(無論是靜態的還是非靜態的) - 都會在StreamInitializer 之後定義它,而StreamInitializer 是透過包含標頭檔案來定義的。然後,StreamInitializer 的建構函式將在Stream 的建構函式之前執行,更糟糕的是,初始化器的解構函式將在Stream 物件的解構函式之後執行。上面的緩衝區解決方案避免了這種情況。

已知用途

[編輯 | 編輯原始碼]

標準 C++ <iostream>std::coutstd::cinstd::cerrstd::clog

[編輯 | 編輯原始碼]

參考文獻

[編輯 | 編輯原始碼]
華夏公益教科書