2009-11-02

C++ initializer list order in a constructor matters?

This is a computer related article, and about THE hardest language, C++. So, I think the audience is again limited.

gcc's -Wall option sometimes gives a warning as the order of initializer list in the constructor is wrong. For example,

% 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.

This warning said that the order of initializer list in the constructor and the member variables declaration order are different. The following code is such an example.

---
#include

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

This warning pointed out that the member initialization order does not related with the order of initializer list in C++. In the C++ book 3rd edition by Stroustrup, Section 10.4.6 Class Objects as Members describes this as "The constructors are 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." This is the meaning of the warning. In the above sample code, the initializer list is

d_j(123),
d_i(d_j - 1).

This looks like the following code,

d_j = 123;
d_i = (d_j - 1);.

But, actually, it is

d_i = (d_j - 1);
d_j = 123;.

Therefore, the value of d_i is undefined. In my environment, the execution result is

i = 32766, j = 123.


Since this is a source of the confusion, I agree the C++ book and gcc warnings. But, the real problem comes from the initialization depends on other member initialization. In such case, I avoid to initialize the member in the initialization list, rather do in the constructor body. It would be more accurate that if the warning detects the dependency between the initializer list.

The answer of the title of this article, "C++ initializer list order in a constructor matters?", would be Yes. By the way, Lisp has let and let* for the block local variable initialization. We can initialize the let function's element in Lisp, and let* is the sequential version, which defines the order of initialization. C++'s initializer list is an implicit version of the let*.

I think it is a bit unnatural in C++ since C++ has only implicit let*, which is based on sequential model. C++'s design is usually tend to execution time optimization. But this definition lead us to make it hard to understand and to be potentially slower. (Or is it a nature of C++?)