Constructor and Destructor Order
The process of creating and deleting objects in C++ is not a trivial task. Every time an
instance of a class is created the constructor method is called. The constructor has the
same name as the class and it doesn't return any type, while the destructor's name it's
defined in the same way, but with a '~' in front:
class String
{
public:
String() //constructor with no arguments
:str(NULL),
size(0)
{
String(int size) //constructor with one argument
:str(NULL),
size(size)
{
str = new char[size];
}
~String() //destructor
{
delete [] str;
};
private:
char *str;
int size;
}
Even if a class is not equipped with a constructor, the compiler will generate code for
one, called the implicit default constructor. This will typically call the default
constructors for all class members, if the class is using virtual methods it is used to
initialize the pointer to the virtual table, and, in class hierarchies, it calls the
constructors of the base classes. Both constructors in the above example
useinitialization lists in order to initialize the members of the class.
The construction order of the members is the order in which they are defined, and for
this reason the same order should be preserved in the initialization list to avoid
confusion.
To master developing and debugging C++ applications, more insight is required
regarding the way constructors and destructors work. Some problems arise when we
are dealing with class hierarchies. Let's take a look at the following example where
class B is inherited from class A:
class A
{
public:
A(int m,int n)
:m(m),
n(n)
{
cout<<"constructor A"<<m<<n;
};
~A()
{
};
private:
int m;
int n;
};
class B : public A
public:
B()
:b(5) //error : default constructor to initialize A is not found
{
cout<<"constructor B"<<b;
}
~B()
{};
private:
int b;
};
int main()
{
B x;
return 0;
};
When we create an object of type B, the A part of the B object must be initialized and
since we provide a constructor for class A, the compiler will not create an implicit
default constructor. This code will fail to compile because there is no default
constructor for class A to be called. To fix this we could provide a default constructor
in class A or explicitly call the existing constructor of A in the initialization list of the
B's constructor:
B()
:A(3,4),
b(5)
{
cout<<"constructor B"<<b;
}
Notice that we needed to call the constructor of A before doing any initialization in B,
since the order of construction starts with the base class and ends with the most
derived class.
Never Use Virtual Methods in Constructors or Destructors
A side effect of this behavior is that you should avoid calling virtual functions in a
class's constructor (or destructor). The problem is that if a base class makes a virtual
function call implemented by the derived class, that function may be implemented in
terms of members that have not yet been initialized (think of the pain that would
cause!). C++ solves this problem by treating the object as if it were the type of the
base class, during the base class constructor, but this defeats the whole purpose of
calling a virtual function in a constructor. Destruction, by the way, works in the same
way. If you're interested, Scott Meyers has a very nice writeup of this principle taken
from his book Effective C++: 55 Specific Ways to Improve Your Programs and
Designs .
The destruction order in derived objects goes in exactly the reverse order of
construction: first the destructors of the most derived classes are called and then the
destructor of the base classes.
Virtual Destructors
To build an object the constructor must be of the same type as the object and because
of this a constructor cannot be a virtual function. But the same thing does not apply to
destructors. A destructor can be defined as virtual or even pure virtual. You would use
a virtual destructor if you ever expect a derived class to be destroyed through a pointer
to the base class. This will ensure that the destructor of the most derived classes will
get called:
A* b1 = new B;
delete b1;
If A does not have a virtual destructor, deleting b1 through a pointer of type A will
merely invoke A's destructor. To enforce the calling of B's destructor in this case we
must have specified A's destructor as virtual:
virtual ~A();
It is a good idea to use a virtual destructor in any class that will be used as
an interface (i.e., that has any other virtual method). If your class has no virtual
methods, you may not want to declare the destructor virtual because doing so will
require the addition of a vtable pointer.
The destructor is called every time an object goes out of scope or when explicitly
deleted by the programmer(using operator delete). The order in which local objects
are implicitly deleted when the scope they are defined ends is the reverse order of
their creation:
void f()
{
string a(.some text.);
string b = a;
string c;
c = b;
}
At the end of function f the destructors of objects c, b, a (in this order) are called and
the same memory is deallocated three times causing undefined and wrong
behavior. Deleting an object more than one time is a serious error. To avoid this issue,
class string must be provided with a copy constructor and a copy assignment operator
which will allocate storage and copy the members by values. The same order of
construction/destruction applies for temporary objects that are used in expressions or
passed as parameters by value. However, the programmer is usually not concerned
with temporary objects since they are managed by the C++ compiler.