|
|
|
|
Destructors and virtual destructors
You cannot use the
virtual keyword with
constructors, but destructors
can and often must be virtual.
The constructor
has the special job of putting an object together piece-by-piece, first by
calling the base constructor, then the more derived constructors in order of
inheritance (it must also call member-object constructors along the way).
Similarly, the destructor has a special job: it must disassemble an object that
may belong to a hierarchy of classes. To do this, the compiler generates code
that calls all the destructors, but in the reverse order that they are
called by the constructor. That is, the destructor starts at the most-derived
class and works its way down to the base class. This is the safe and desirable
thing to do because the current destructor can always know that the base-class
members are alive and active. If you need to call a base-class member function
inside your destructor, it is safe to do so. Thus, the destructor can perform
its own cleanup, then call the next-down destructor, which will perform
its own cleanup, etc. Each destructor knows what
its class is derived from, but not what is derived from
it.
You should keep in mind that constructors
and destructors are the only places where this hierarchy of calls must happen
(and thus the proper hierarchy is automatically generated by the compiler). In
all other functions, only that function will be called (and not
base-class versions), whether it’s virtual or not. The only way for
base-class versions of the same function to be called in ordinary functions
(virtual or not) is if you explicitly call that
function.
Normally, the action of the destructor is
quite adequate. But what happens if you want to manipulate an object through a
pointer to its base class (that is, manipulate the object through its generic
interface)? This activity is a major objective in object-oriented programming.
The problem occurs when you want to delete a pointer of this type for an
object that has been created on the heap with new. If the pointer is to
the base class, the compiler can only know to call the base-class version of the
destructor during delete. Sound familiar? This is the same problem that
virtual functions were created to solve for the general case. Fortunately,
virtual functions work for destructors as they do for all other functions except
constructors.
//: C15:VirtualDestructors.cpp
// Behavior of virtual vs. non-virtual destructor
#include <iostream>
using namespace std;
class Base1 {
public:
~Base1() { cout << "~Base1()\n"; }
};
class Derived1 : public Base1 {
public:
~Derived1() { cout << "~Derived1()\n"; }
};
class Base2 {
public:
virtual ~Base2() { cout << "~Base2()\n"; }
};
class Derived2 : public Base2 {
public:
~Derived2() { cout << "~Derived2()\n"; }
};
int main() {
Base1* bp = new Derived1; // Upcast
delete bp;
Base2* b2p = new Derived2; // Upcast
delete b2p;
} ///:~
When you run the program, you’ll
see that delete bp only calls the base-class destructor, while delete
b2p calls the derived-class destructor followed by the base-class
destructor, which is the behavior we desire. Forgetting to make a destructor
virtual is an insidious bug because it often doesn’t directly
affect the behavior of your program, but it can quietly introduce a memory leak.
Also, the fact that some destruction is occurring can further mask the
problem.
Even though the destructor, like the
constructor, is an “exceptional” function, it is possible for the
destructor to be virtual because the object already knows what type it is
(whereas it doesn’t during construction). Once an object has been
constructed, its VPTR is initialized, so virtual function calls can take
place.
|
|
|