In all other functions, only that function will be called, 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 is certainly 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.
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.
For a time, pure virtual destructors were legal and worked if you combined them with a
function body, but in the final C++ standard function bodies combined with pure virtual
functions were outlawed. This means that a virtual destructor cannot be pure, and must have a function body because (unlike ordinary functions) all destructors in a class hierarchy are
always called. Here’s an example:
//: C15:Pvdest.cpp
// Pure virtual destructors
Chapter 13: Polymorphism & Virtual Functions
470
// require a function body.
#include <iostream>
using namespace std;
class Base {
public:
virtual ~Base() {
cout << "~Base()" << endl;
}
};
class Derived : public Base {
public:
~Derived() {
cout << "~Derived()" << endl;
}
};
int main() {
Base* bp = new Derived; // Upcast
delete bp; // Virtual destructor call
} ///:~
As a guideline, any time you have a virtual function in a class, you should immediately add a virtual destructor (even if it does nothing). This way, you ensure against any surprises later.
Virtuals in destructors
There’s something that happens during destruction that you might not immediately expect. If
you’re inside an ordinary member function and you call a virtual function, that function is
called using the late-binding mechanism. This is not true with destructors, virtual or not.
Inside a destructor, only the “local” version of the member function is called; the virtual
mechanism is ignored.
Why is this? Suppose the virtual mechanism were used inside the destructor. Then it would be possible for the virtual call to resolve to a function that was “further out” (more derived) on the inheritance hierarchy than the current destructor. But destructors are called from the
“outside in” (from the most-derived destructor down to the base destructor), so the actual
function called would rely on portions of an object that has already been destroyed! Thus, the compiler resolves the calls at compile-time and calls only the “local” version of the function.
Notice that the same is true for the constructor (as described earlier), but in the constructor’s case the information wasn’t available, whereas in the destructor the information (that is, the VPTR) is there, but is isn’t reliable.
Chapter 13: Polymorphism & Virtual Functions
471
Summary
Polymorphism – implemented in C++ with virtual functions – means “different forms.” In
object-oriented programming, you have the same face (the common interface in the base
class) and different forms using that face: the different versions of the virtual functions.
You’ve seen in this chapter that it’s impossible to understand, or even create, an example of polymorphism without using data abstraction and inheritance. Polymorphism is a feature that
|