再び計算機ネタである.しかも C++ なので一般向けではないだろう.
gcc の -Wall を使っていると,コンストラクタの initializer list の初期化の順番が違うという warning がでることがある.例えば,
% g++ -Wall initializer_list.cc
initializer_list.cc: In constructor 'InitializerListTest::InitializerListTest()':
initializer_list.cc:30: warning: 'InitializerListTest::d_j' will be initialized after
initializer_list.cc:29: warning: 'int InitializerListTest::d_i'
initializer_list.cc:7: warning: when initialized here
のようなものである.これは class の内部で宣言されている member variable の順番とコンストラクタの initializer list の書かれている順番が違うという warning である.以下のようなコードで起こる.
---
#include <iostream>
class InitializerListTest {
public:
/// constructor
InitializerListTest() :
d_j(123), // j = 123;
d_i(d_j - 1) // i = j - 1;
{
// empty
}
/// print out
void print()
{
std::cout << "i = " << d_i << ", j = " << d_j << std::endl;
}
private:
/// i, j
int d_i;
int d_j;
};
int main()
{
InitializerListTest ilt;
ilt.print();
return 0;
}
---
この warning は,C++ では initializer の list と各メンバの constructorの呼び出しの順番には関係がないことに起因している.Stroustrup の C++,3rd edition の 10.4.6 Class Objects as Members には The constructorsare called in the order in which they are declared in the class rather than the order in which they appear in the initializer list. To avoid confusion, it is best to specify the initializers in declaration order. とある.私は日本語の訳の本を持っていないので,どう訳されているか知らないが,「(メンバの)コンストラクタの呼び出しは,initializer list に書かれている順番ではなく,クラス内で宣言された順番で行なわれる.混乱しないように,initializer は宣言された順番に書くのが良いだろう.」となるだろう.これが warningの意味である.上記のサンプルコードでは,initializer list は
d_j(123),
d_i(d_j - 1)
となっているので,一見すると,
d_j = 123;
d_i = (d_j - 1);
のように見えるが,実際に実行されるのは,
d_i = (d_j - 1);
d_j = 123;
の順番であり,d_i の値は不定である.私の環境では,このプログラムを実行させると,
i = 32766, j = 123
という結果になった.これは確かに混乱のもとであるので,gcc は warning を出すのであろう.ただし,このようにメンバは初期化の順番に依存するコードを initializer list に書くこと自体,私は避けることにしている.必要な場合にはコンストラクタの block の内部で行えば良い.初期化の順番にdependency があるものを initializer list に書いている,というようなwarning にしてもらった方がより嬉しい気がする.
タイトルの「C++ のコンストラクタにおける initializer list の順番は重要なのか?」に対しては C++ では Yes と答えて良いと思う.ところで,Lisp であれば,これは let と let* の違いである.Lisp の let はそれぞれのinitialize を並列に行なってもかまわず,let* は sequential である.
C++ では混乱しやすい implicit な let* しかなく,並列実行可能な let がないというのは実行速度至上主義の C++ としてはちょっと不自然な気もする.C++ の定義では難しい上に遅くなる傾向があって良い所がみあたらない.(それともそれも C++ の特性なのか?)