meryngii.neta

今日も新たな"ネタ"を求めて。

初期化順

main関数に入る前の厄介なバグに出会った。調べていくうちに初期化順の問題だということが分かった。
グローバルなオブジェクトの動的初期化の順番が不定なのは私も知っている。D&Eの3.11.4.1ではcoutやcinが動的初期化に頼っていることによる問題点について解説している。
例えばこんなコードを考えてみる。

extern ostream cout;
// ...
struct my_class
{
	my_class() { cout << "abc"; }
}
my_instance;

coutがmy_instanceの前に初期化されるかどうかについては未定義だから、このコードは正常に動かないこともありうる。*1
この問題はまだ分かる。コンパイラから見ればどちらを先に初期化するべきか分からないからだ。*2
今回はまったコードはこれ。

struct A
{
	A() : n(1234) {  }
	int n;
};

struct B
{
	B() { std::cout << a.n; }
	static A a;
};

B b;
A B::a;

さっきは兄弟の関係だったが、こっちは親子の関係だ。私はbのコンストラクタで一緒にaが初期化されるのだろうと思っていたが、よく考えればaは一つだけBは複数ありうるからそれはできない。コンパイラから見ればさっきの状況と変わらないのだ。
More C++ Idiomsにこれと同じことが書いてあった。だからプロの方は当然のようにご存じなのかもしれない。
http://ja.wikibooks.org/wiki/More_C%2B%2B_Idioms/%E5%88%9D%E5%9B%9E%E4%BD%BF%E7%94%A8%E6%99%82%E7%94%9F%E6%88%90(Construct_On_First_Use)
今回はローカル静的オブジェクトの解決策を使うことにした。

struct B
{
	B() { std::cout << a().n; }
	A& a() { static A a_; return a_; }
};

これは静的メンバが長ったらしいことにも効果がある。ただし、後ろにカッコが必要なのは致し方ない。
この解決策にはいくつか問題点がある。そのことについてModern C++ Designの6章のSingletonの実装にかなり詳しく書いてある。デストラクタの呼び出しの順番、マルチスレッド環境などなど面倒なことだらけだ。こういう一見どうでもよさそうな問題は根が深い。

*1:コンパイラも進化しているので、現代の標準ライブラリではしっかりと対策がされているだろう。ただ、お粗末な自作ライブラリについてはこの限りではない。

*2:リンカの段階で依存関係を洗い出せば分からないというわけではない。