This page last updated on:
May 16, 2001
Contents and Related Links
CodeWrangler Pages
|
Overview
As a general rule, a destructor should be declared
virtual in a base class. There is a potential problem when destroying derived
objects which are treated as base objects. The problem is that the destructor
for the derived object is not invoked when the derived object is destroyed
through a base pointer.
Assume a class D is derived from a base class B. There
is a destructor defined for D, and the B destructor is
not declared virtual. First a simple example to point out the pitfall:
void f (B* b_ptr) {
// ... call some B functions
delete b_ptr;
}
int main () {
// ...
if (some_condition) {
B* b_ptr = new B;
// ... do something with the B object
f (b_ptr);
}
else {
D* d_ptr = new D;
// ... do something with the D object
f (d_ptr); // perfectly acceptable to pass a derived pointer as a base pointer
// Ack!! may have problems from this point on
}
}
The second call of f will produce undesired behavior, as D's
destructor is not called. This can be a subtle, hard-to-find, and
potentially disastrous bug.
On a side note, a recommended design guideline is to always new
and delete objects at the same functional level. The logic used
in this simple example creates and destroys objects at two different levels
of the call hierarchy rather than the same one. Keeping object allocation
and deallocation at the same conceptual level makes for a simpler design
and for easier debugging and maintenance.
A more realistic example without the bad design style:
const B* find_oldest(const B* b_array[]) { // find oldest object in B ptr array
// ... selection code
return b_array[selected_index];
}
int main () {
// ...
B* b_array[MAX_SIZE];
// ... create the list of B and D object pointers (and possibly containing other
// derived object pointers) by newing B or D (or other) objects
B* selected_ptr = find_oldest (b_array);
delete selected_ptr;
}
If the selected (oldest) object which is deleted is a derived object, it's
destructor is not called, which can make for a very hard to detect bug.
To avoid this pitfall, some developers declare destructors virtual in
all classes. My opinion is that blindly following this rule will add overhead
in classes that can't afford it. Remember that a virtual function in a
class will add an extra word of storage to any objects created of that
type. In addition, when a virtual function is called, an extra level of
indirection is needed for the invocation of that function. For many classes,
this space and performance penalty can be prohibitive. For example, a complex
number or pixel encapsulation class would not want virtual destructors,
since they are typically or frequently allocated in large arrays or containers.
Many base classes, however, already have one or more virtual functions
declared. In this case, declaring another virtual function does not add
any additional overhead. Also, base classes with virtual functions are
more likely to be used in situations where the problem can occur.
My recommendation is to declare destructors virtual in base classes,
as long as the overhead is not going to be an issue. If the class is stand-alone
and will not be used as a base class, then I don't declare the destructor
as being virtual. If there's already at least one virtual function in the
base class, then the destructor is always declared virtual.
|