I just remembered one other important property of initializer lists which could influence code semantics, so I thought I'd mention it.
Consider the following code:
class Base {
private:
double x_;
int y_;
public:
int virtual MyMagicNumber() const;
Base(double x) : x_(x) {
y_ = MyMagicNumber();
}
};
class derived : public Base {
private:
int i_;
public:
virtual int MyMagicNumber() const { return i_+1; }
derived(int i) : Base((double(i)/2.0) {
i_ = i; }
};
void Foo() {
derived d(3);
}
Upon calling the constructor of derived, the following happens:
0. if no other instance of class
derived
has been created before, vtables will be created in memory
1. Memory gets allocated. This memory is uninitialized, it may not even be set to 0.
2. Base gets initialized, because it's on the initializer list
3.
Base::x_
gets initialized to 1.5, because it is on the initializer list of
Base::Base(double)
4. The code of
Base::Base(double)
gets executed
5.
MyMgicNumber()
gets called, via the vtable, where it gets resolved to
derived::MyMagicNumber()
6.
derived::MyMagicNumber()
reads
derived::i_
and then returns i_+1
7.
Base::Base(double)
initializes
Base::y_
with the result from
MyMagicNumber()
. The constructor is done.
8. With
Base::Base(double)
done and nothing else on the initializer list, the code from
derived::derived(int)
finally gets executed
9.
derived::i_
gets initialized to 3
So, the very last thing that happens during construction is the initialization of
derived::i_
, but some time before that, it already gets read! As a result, the state of
Base::y_
is undefined! Of course, the true problem is that you shouldn't call virtual functions in your constructor code, but you can fix it, easily, by putting i on the initialization list as well:
derived(int i) i_(i), Base((double)i/2.0) {}
There. This moves the initialization of
derived::i_
right up to position 2, and now the following initialization of
Base::y_
will be well-defined!
In short, the initialization list is the only way to initialize class members before calling base class constructors! What you need to keep in mind is initialization order:
- First the initialization list, left to right (this may include direct base classes)
- Second any constructors for base classes not already listed on the initialization list (don't ask for virtual base classes, I'd need to look that up)
- Last the actual code in the constructor body