|
|
|
|
Order of constructor & destructor calls
It’s interesting to know the order
of constructor and destructor calls
when an
object has many subobjects. The following example shows exactly how it
works:
//: C14:Order.cpp
// Constructor/destructor order
#include <fstream>
using namespace std;
ofstream out("order.out");
#define CLASS(ID) class ID { \
public: \
ID(int) { out << #ID " constructor\n"; } \
~ID() { out << #ID " destructor\n"; } \
};
CLASS(Base1);
CLASS(Member1);
CLASS(Member2);
CLASS(Member3);
CLASS(Member4);
class Derived1 : public Base1 {
Member1 m1;
Member2 m2;
public:
Derived1(int) : m2(1), m1(2), Base1(3) {
out << "Derived1 constructor\n";
}
~Derived1() {
out << "Derived1 destructor\n";
}
};
class Derived2 : public Derived1 {
Member3 m3;
Member4 m4;
public:
Derived2() : m3(1), Derived1(2), m4(3) {
out << "Derived2 constructor\n";
}
~Derived2() {
out << "Derived2 destructor\n";
}
};
int main() {
Derived2 d2;
} ///:~
First, an
ofstream object is created to send all the output
to a file. Then, to save some typing and demonstrate a macro technique that will
be replaced by a much improved technique in Chapter 16, a
macro is created to build some
of the classes, which are then used in inheritance and composition. Each of the
constructors and destructors report themselves to the trace file. Note that the
constructors are not default constructors; they each have an int
argument. The argument itself has no identifier; its only reason for existence
is to force you to explicitly call the constructors in the initializer list.
(Eliminating the identifier prevents compiler warning
messages.)
The output of this program
is
Base1 constructor
Member1 constructor
Member2 constructor
Derived1 constructor
Member3 constructor
Member4 constructor
Derived2 constructor
Derived2 destructor
Member4 destructor
Member3 destructor
Derived1 destructor
Member2 destructor
Member1 destructor
Base1 destructor
You can see that construction starts at
the very root of the class hierarchy, and that at each level the base class
constructor is called first, followed by the member object constructors. The
destructors are called in exactly the reverse order of the constructors –
this is important because of potential dependencies (in the derived-class
constructor or destructor, you must be able to assume that the base-class
subobject is still available for use, and has already been constructed –
or not destroyed yet).
It’s also interesting that the
order of constructor calls for member objects is completely unaffected by the
order of the calls in the constructor initializer list. The order is determined
by the order that the member objects are declared in the class. If you could
change the order of constructor calls via the constructor initializer list, you
could have two different call sequences in two different constructors, but the
poor destructor wouldn’t know how to properly reverse the order of the
calls for destruction, and you could end up with a dependency
problem.
|
|
|