Customizing a Class with Polymorphism
Polymorphism (from the Greek for “having many forms”) is what happens when you assign different meanings to a symbol or operator in different contexts. All well and good — but what does it mean to us as C++ programmers? Granted, the pure virtual function in C++ (discussed in Technique 2) is very useful, but C++ gives us an additional edge: The programmer can override only selected pieces of a class without forcing us to override the entire class. Although a pure virtual function requires the programmer to implement functionality, a virtual function allows you to override that functionality only if you wish to, which is an important distinction.
Small changes to the derived class are called virtual functions — in effect, they allow a derived class to override the functionality in a base class without making you tinker with the base class. You can use this capability to define a given class’s default functionality, while still letting end users of the class fine-tune that functionality for their own purposes. This approach might be used for error handling, or to change the way a given class handles printing, or just about anything else. In the next sectin, I show you how you can customize a class, using virtual functions to change the behavior of a base-class method at run-time.
Testing the Virtual Function Code
There are a few interesting things to note in this example. For one thing, you can see how the base class calls the overridden methods without having to “know” about them (see the lines marked 1 and 2). What does this magic is a lookup table for virtual functions (often called the vtable) that contains pointers to all the methods within the class. This table is not visible in your code, it is automatically generated by thus you can see, the direct calls to the code work fine. In addition, you can see that the code that uses a base-class pointer to access the functionality in the derived classes does call the proper overridden virtual methods. This leaves us with only one question remaining, which is how the derived class destructors.
Why Do the Destructors Work?
The interesting thing here is that the destructor for the base class is always called. Because the destructtor is declared as virtual, the destructor chains upward through the destructors for the other classes that are derived from the base class. If we created destructors for each derived class, and printed out the results, then if you created a new purple grape green grape class, for example, that was derived from Grape; you would see output that looked like this:
This output would be shown from the line in which we deleted the purple grape green grape object. This chaining effect allows us to allocate data at each stage of the inheritance tree — while still ensuring that the data is cleaned up appropriately for each level of destructor. It also suggests the following maxim for writing code for classes from which other classes can be derived:
Notice also that the virtual table for a base class can be affected by every class derived from it (as we can see by the green grape class). When I invoke the Print method on a Fruit object that was created as a Grape-derived green grape class, the method is invoked at the Grape class level. This means you can have as many levels of inheritance as you like. As you can see, the virtual-method functionality in C++ is extremely powerful. To recap, here is the hierarchy for calling the correct Print method when a green grape object is passed to a function that accepts a Fruit object.