Time for another puzzle! What is the output of this program?
class Parent { public: Parent(int p=0.0) : p(p) { cout << "Parent(" << p << ")" << endl; } int p; }; class Member { public: Member(int m=0.0) : m(m) { cout << "Member(" << m << ")" << endl; } Member& operator=(const Member& rhs) { cout << "Member is copied" << endl; m = rhs.m; return *this; } int m; }; class Derived : public Parent { public: Derived() : foo(10), bar(foo*2) { Parent(7); m = Member(42); } int bar; int foo; Member m; }; int main() { Derived d; cout << d.p << " " << d.foo << " " << d.bar << " " << d.m.m << endl; }
Answer: Undefined behaviour! Which means it could output whatever, crash at any point, or format your hard drive. Can you spot the source of the undefined behaviour? Hint: Look for the use of an uninitialized variable.
Did you see that the order of foo
and bar
in the initializer list is not in the same order as their declarations? Members are always initialized in the order of declaration, not in the order in the initialization list. This means bar
is initialized before foo
, using an uninitialized foo
to compute its value. If you compile with all warnings enabled (always a good idea), your compiler should warn you about this. For instance, g++ tells me:
main.cpp: In constructor ‘Derived::Derived()’: main.cpp:29:9: warning: ‘Derived::foo’ will be initialized after [-Wreorder] main.cpp:28:9: warning: ‘int Derived::bar’ [-Wreorder] main.cpp:22:5: warning: when initialized here [-Wreorder]
Next question then, what is the likely output of this program on a real compiler? On my Ubuntu 12.04 using g++ 4.6, I get:
Parent(0) Member(0) Parent(7) Member(42) Member is copied 0 10 8393920 42
Lets’ walk through what happens here.
- 1:
Derived
inherits fromParent
. C++ guarantees that all parent objects are fully constructed before the constructor of the derived class is invoked, so the first thing that happens is that the default constructor ofParent
is called. (Did I fool you withParent(7);
in the constructor though? That line will just create a local object that is never used. Had I moved it to the initializer list, it would have been used instead of the default constructor.) - 2: Before the body of
Derived
‘s constructor, all its members will be initialized. Since we don’t specifically initializeMember m
in the initializer list, it is first automatically default constructed, and then re-assigned in the body of the constructor (as seen in line 5 of the output). - 3: On line 34 of the program, we create a local
Parent
object which is never used. Maybe we meant to setp
to7
, but put theParent()
call in the wrong place? - 4-5: Now another
Member
is constructed, and copy assigned tom
. All this re-construction and copying could have been avoided if we had moved the callMember(42)
to the initializer list. - 6: Finally, we have a look at the resulting values of
Derived
‘s members:p
is0
, not7
as we maybe meant it to be, as explained in 1.foo
is10
, hopefully as expected.bar
is8393920
, due to the use of the uninitializedfoo
, as explained earlier. This is entirely by chance, and should not be relied on! Your program might just as well crash, or do something worse.
Finally let’s clean up the program and see what happens. We reorder the declarations of foo
and bar
, and move all initializations to the initializer list:
ERROR: Couldn't open file: [Errno 2] No such file or directory: '/home/anders/Documents/code/blog/initialization/main2.cpp'
Now the program is well defined, and free of repeated constructors and copying. Here is the output:
Parent(7) Member(42) 7 10 20 42
Much better! Full source here.
As usual, the code for this blog post is available on GitHub.
If you enjoyed this post, you can subscribe to my blog, or follow me on Twitter.