シュワルツカウンタ

最近本を読んでいたらシュワルツカウンタを使って、Static メンバ変数の初期化順序を保証するという話が出てきました。シュワルツカウンタ自体は C++ で静的変数の利用前に初期化され、利用中に廃棄しないことを保証する仕組みらしいです。

この仕組みは More C++ Idioms で紹介されています。この記事によると、std::cout もシュワルツカウンタで初期化されているとのことです。

実際に記事のサンプルをたよりに作ってみると以下のようになりました。

#ifndef HOGE_INITALIZER_H
#define HOGE_INITALIZER_H

class HogeInitializer;

class Hoge {
friend class HogeInitializer;

public:
static int* global_count;
private:
Hoge () {}
};

class HogeInitializer {
public:
HogeInitializer ();
~HogeInitializer ();
};

static HogeInitializer initializer; // NOTE!

#endif //HOGE_INITALIZER_H

HogeInitializer のインスタンス initializer がヘッダファイル (hoge.hpp)で定義されている点がミソです。 HogeInitalizer は Hoge の static 変数 global_count を初期化します。具体的には以下のように定義されています。

#include "hoge.hpp"

static int nifty_counter = 0;
int* Hoge::global_count = 0;

HogeInitializer::HogeInitializer ()
{
if (0 == nifty_counter++)
{
// Initialize the statics in Hoge
Hoge::global_count = new int(1);
}
}

HogeInitializer::~HogeInitializer ()
{
if (0 == --nifty_counter)
{
delete Hoge::global_count;
// delete statics in Hoge if needed.
}
}

hoge.hpp を include した翻訳単位で HogeInitializer が生成されます。そのたびに nifty_counter がインクリメントされますが、はじめの呼び出しのときだけ、static 変数への代入がおこります。逆に Hoge が利用されなくなると、nifty_counter の値はデクリメントされ、最後の呼び出しのときだけ static 変数が削除されるので、static 変数を複数の場所で使用していても、変数の生存が保証されるそうです。ほへ〜〜。

#include
#include "hoge.hpp"

int main(int argc, char** argv) {
std::cout << "main: couter is " << *Hoge::global_count << std::endl;
}

Hoge を利用するフィアルはhoge.hpp をインクルードすることにより、Hogeの初期化クラス(HogeInitializer) のインスタンスを読み込むことになるため、必ず global_count の初期化は済んでいることになります。main 関数の外等で Hoge::global_count を利用する様に書き直しても、期待通り利用前に初期化されデストラクタが呼ばれた後に global_count がdelete されるのが確認できました。

さて自分の環境の(gcc 4.2.1)の iostream を読むと、ios_base::init_ioinit という static 変数が宣言があり、cout, cin 等の初期化に利用されています。

// For construction of filebuffers for cout, cin, cerr, clog et. al.
static ios_base::Init __ioinit;

Initのコンストラクタを見ると、確かにシュワルツカウンタが利用されていることが確認できます(間違っていたらすみませぬ)。

ios_base::Init::Init()
{
if (++_S_ios_base_init == 1)
{
// Standard streams default to synced with "C" operations.
ios_base::Init::_S_synced_with_stdio = true;
_S_ios_create(ios_base::Init::_S_synced_with_stdio);
}
}