Welcome to this quick post on constructor initialization lists in C++. If you are not familiar with initialization lists here is a very short introduction. When in C++ a class is instantiated, first, all base classes and second, all class attributes are constructed. Initialization lists are used to control that process. In initialization lists the constructors of the base class and the class attributes can be explicitly called. Otherwise, they are initialized by calling their default constructor. For efficiency reasons it is important to use initialization lists, because all member initialization takes place before the body of the constructor is entered. So much about the basics of initialization lists. Now a little riddle. The following piece of code will crash. Can you find the problem? Give it a try your-self before you continue reading.
struct Foo { Foo(int newId) : id(newId) {} int id; }; struct Bar { Bar(const Foo &newFoo) : foo(newFoo), fooId(foo.id) {} const int fooId; const Foo & foo; }; int main(int argc, char **argv) { Foo foo(303); Bar bar(foo); return 0; }
If you don’t know the nasty details of how class attributes are initialized via initialization lists, you most probably couldn’t figure out what causes the crash in those few lines of code. The issue with the code above is that the order of attribute initialization is not determined by the order of appearance in the initialization list, but by the order of declaration within the class. Note, that foo
appears in the initialization list before fooId
, but fooId
is declared before foo
. Hence, not foo
but fooId
is initialized first, accessing the still uninitialized attribute foo
.
So, why is that you might ask? This, on the first glance, strange behavior actually makes a lot of sense. When an object is destroyed, its destructor calls the destructors of every class attribute in the reverse order they were initialized. As there can be potentially more than one constructor with different orders of attribute initialization the order of destruction wouldn’t be defined. To solve the ambiguity simply the order of declaration is used.
March 31, 2019 at 9:47 pm
Hi Blenz3,
Below code, same as above, compiles where order of declaration and initialization is different.
#include
struct Bar {
Bar( float f, int x)
: fl(f), fooId(x)
{
}
};
int main(int argc, char** argv)
{
}
May 28, 2020 at 12:08 am
Two things; 1) the example code will compile, probably with a warning, but will crash on execution. 2) your code is not the same; where fooId is referenced in the Bar() initializer list, it uses the “id” field of its own “foo” object (fooId(foo.id)). This is relying on “foo” being initialized BEFORE fooId but, because of the rules of C++, that doesn’t happen.