Wrox Press C++ Tutorial


Virtual Functions

We need to look more closely at the behavior of inherited member functions and their relationship with derived class member functions. Let's add a function to the class Box to output the volume of a Box object. The simplified class would then become:

// Listing 8_06-01
class Box            // Base class
{
   public:
      // Function to show the volume of an object
      void ShowVolume()
      {
         cout << endl
              << "Box usable volume is " << Volume(); 
      }

      // Function to calculate the volume of a Box object
      double Volume()
      { return length*breadth*height; }

      // Constructor
      Box(double lv=1.0, double bv=1.0, double hv=1.0)
                        :length(lv), breadth(bv), height(hv) {}

   protected:
      double length;
      double breadth;
      double height;
};

Now we can output the usable volume of a Box object just by calling the ShowVolume() function of any object for which we require it. The constructor sets the data member values in the initialization list, so no statements are necessary in the body of the function. The data members are as before and are specified as protected, so they will be accessible to the member functions of any derived class.

Now, let's suppose we want to derive a class for a different kind of box called GlassBox, to hold glassware. The contents are fragile, and because packing material is added to protect them, the capacity of the box is less than the capacity of a basic Box object. We therefore need a different Volume() function to account for this, so we add it to the derived class:

// Listing 8_06-02
class GlassBox: public Box     // Derived class
{
   public:
      // Function to calculate volume of a GlassBox
      // allowing 15% for packing

      double Volume()
      { return 0.85*length*breadth*height; }

      // Constructor
      GlassBox(double lv, double bv, double hv): Box(lv,bv,hv){}
};

There could conceivably be other additional members of the derived class, but we'll keep it simple and concentrate on how the inherited functions work for the moment. The constructor for the derived class objects just calls the base class constructor in its initialization list to set the data member values. No statements are necessary in its body. We've included a new version of the function Volume() to replace the version from the base class, the idea being that we can get the inherited function ShowVolume() to call the derived class version of the member function Volume() when we call it for an object of the class GlassBox.

Try It Out - Using an Inherited Function

Now we should see how our derived class works in practice. We can try this out very simply by creating an object of the base class and an object of the derived class with the same dimensions, and then verifying that the correct volumes are being calculated. The main() function to do this would be as follows:

// Ex8_06.cpp
// Behavior of inherited functions in a derived class
#include <iostream>
using namespace std;

// Insert Box class definition (Listing 8_06-01)

// Insert GlassBox class definition (Listing 8_06-02)

int main()
{
   Box myBox(2.0, 3.0, 4.0);           // Declare a base box
   GlassBox myGlassBox(2.0, 3.0, 4.0); // Declare derived box

   myBox.ShowVolume();          // Display volume of base box
   myGlassBox.ShowVolume();     // Display volume of derived box

   cout << endl;
   return 0;
}

How It Works

If you run this example, it will produce the following output:

This isn't only dull and repetitive, it's also disastrous. It isn't working the way we want at all, and the only interesting thing about it is why. Evidently, the fact that the second call is for an object of the derived class GlassBox is not being taken into account. We can see this from the incorrect result in the output. The volume of a GlassBox object should definitely be less than that of a basic Box with the same dimensions.

The reason for the incorrect output is that the call of the function Volume() in the function ShowVolume() is being set once and for all by the compiler as the version defined in the base class. This is called static resolution of the function call, or static linkage - the function call is fixed before the program is executed. This is also sometimes called early binding because the particular Volume() function chosen is bound to the call from the function ShowVolume() during the compilation of the program.

The function Volume() here in the derived class actually hides the base class version from the view of derived class functions. If you wanted to call the base version of Volume() from a derived class function, you would need to use the scope resolution operator to refer to the function as Box::Volume().

What we were hoping for in this example was that the question of which Volume() function call to use in any given instance would be resolved when the program was executed. This sort of operation is referred to as dynamic linkage, or late binding. We want the actual version of the function Volume() called by ShowVolume() to be determined by the kind of object being processed, and not arbitrarily fixed by the compiler before the program is executed.

No doubt you'll be less than astonished that C++ does, in fact, provide us with a way to do this, since this whole discussion would have been futile otherwise! We need to use something called a virtual function.

What's a Virtual Function?

A virtual function is a function in a base class that is declared using the keyword virtual. Defining in a base class a virtual function, with another version in a derived class, signals to the compiler that we don't want static linkage for this function. What we do want is the selection of the function to be called at any given point in the program to be based on the kind of object for which it is called.

Try It Out - Fixing the GlassBox

To make our example work as we originally hoped, we just need to add the keyword virtual to the definitions of the Volume() functions:

// Ex8_07.cpp (based on Ex8_06.cpp)
// Using a virtual function 
#include <iostream>
using namespace std;

class Box            // Base class
{
   public:
...
      // Function to calculate the volume of a Box object
      virtual double Volume()
      { return length*breadth*height; }
...
};

class GlassBox: public Box
{
   public:
...
      // Function to calculate volume of a GlassBox
      // allowing 15% for packing
      virtual double Volume()
      { return 0.85*length*breadth*height; }
...
};

int main()
{
...
}

How It Works

If you run this version of the program with just the little word virtual added to the definitions of Volume(), it will produce this output:

This is now clearly doing what we wanted in the first place. The first call to the function ShowVolume() with the Box object, myBox, calls the base version of Volume(). The second call with the GlassBox object, myGlassBox, calls the version defined in the derived class.

Note that, although we have put the keyword virtual in the derived class definition of the function Volume(), it's not essential to do so. The definition of the base version of the function as virtual is sufficient. However, I recommend that you do specify the keyword for virtual functions in derived classes, since it makes it clear to anyone reading the derived class definition that they are virtual functions and that they will be selected dynamically.

In order for a function to behave as virtual, it must have the same name, parameter list, and return type in any derived class as the function has in the base class. If you try to use different parameters or return types, the virtual function mechanism won't work. The function will operate with static linkage established and fixed at compile time.

The operation of virtual functions is an extraordinarily powerful mechanism. You may have heard the term polymorphism in relation to object-oriented programming, and this refers to the virtual function capability. Something that is polymorphic can appear in different guises, like a werewolf, or Dr. Jekyll, or a politician before and after an election, for example. Calling a virtual function will produce different effects depending on the kind of object for which it is being called.

Using Pointers to Class Objects

Using pointers with objects of a base class and of a derived class is a very important technique. A pointer to a base class object can be assigned the address of a derived class object as well as that of the base. We can thus use a pointer of the type 'pointer to base' to obtain different behavior with virtual functions, depending on what kind of object the pointer is pointing to. We can see how this works more clearly by looking at an example.

Try It Out - Pointers to Base and Derived Classes

Let's use the same classes as in the previous example, but make a small modification to the function main() so that it uses a pointer to a base class object.

// Ex8_08.cpp
// Using a base class pointer to call a virtual function
#include <iostream>
using namespace std;

class Box        // Base class
{
   public:
      // Function to show the volume of an object
      void ShowVolume()
      {
         cout << endl
              << "Box usable volume is " << Volume(); 
      }

      // Function to calculate the volume of a Box object
      virtual double Volume()
      { return length*breadth*height; }

      // Constructor
      Box(double lv=1.0, double bv=1.0, double hv=1.0)
                         :length(lv), breadth(bv), height(hv) {}

   protected:
      double length;
      double breadth;
      double height;
};

class GlassBox: public Box     // Derived class
{
   public:
      // Function to calculate volume of a GlassBox
      // allowing 15% for packing
      virtual double Volume()
      { return 0.85*length*breadth*height; }

      // Constructor
      GlassBox(double lv, double bv, double hv): Box(lv,bv,hv){}
};

int main()
{
   Box myBox(2.0, 3.0, 4.0);         // Declare a base box
   GlassBox myGlassBox(2.0,3.0,4.0); // Declare derived box

   Box* pBox = 0;      // Declare a pointer to base class objects
   pBox = &myBox;      // Set pointer to address of base object

   pBox->ShowVolume(); // Display volume of base box
   pBox = &myGlassBox; // Set pointer to derived class object
   pBox->ShowVolume(); // Display volume of derived box

   cout << endl;
   return 0;
}

How It Works

The classes are the same as in example Ex8_07.cpp, but the function main() has been altered to use a pointer to call the function ShowVolume(). Because we are using a pointer, we use the indirect member selection operator, ->, to call the function. The function ShowVolume() is called twice, and both calls use the same pointer to base class objects, pBox. On the first occasion, the pointer contains the address of the base object, myBox, and on the occasion of the second call, it contains the address of the derived class object, myGlassBox.

The output produced is as follows:

This output is exactly the same as that from the previous example, where we used explicit objects in the function call.

We can conclude from this example that the virtual function mechanism works just as well through a pointer to a base class, with the specific function being selected based on the type of object being pointed to. This is illustrated in the following figure.

This means that, even when we don't know the precise type of the object pointed to by a base class pointer in a program (when a pointer is passed to a function as an argument, for example), the virtual function mechanism will ensure that the correct function is called. This is an extraordinarily powerful capability, so make sure you understand it. Polymorphism is a fundamental mechanism in C++ that you will find yourself using again and again.

Using References With Virtual Functions

If you define a function with a reference to a base class as a parameter, you can pass an object of a derived class to it as an argument. When your function executes, the appropriate virtual function for the object passed will be selected automatically. We could show this happening by modifying the function main() in the last example to call a function that has a reference as a parameter.

Try It Out - Using References with Virtual Functions

Let's move the call to ShowVolume() to a separate function, and call that separate function from main():

// Ex8_09.cpp
// Using a reference to call a virtual function
#include <iostream>
using namespace std;

class Box;  // Incomplete class definition required for prototype
void Output(Box& aBox); // Prototype of function

// Insert class definitions for Box and GlassBox 
// (as in Ex8_08.cpp)

int main()
{
   Box myBox(2.0, 3.0, 4.0);          // Declare a base box
   GlassBox myGlassBox(2.0,3.0,4.0);  // Declare derived box 

   Output(myBox);      // Output volume of base class object
   Output(myGlassBox); // Output volume of derived class object

   cout << endl;
   return 0;
}

// Function to output a volume via a virtual function 
// call using a reference
void Output(Box& aBox)
{
   aBox.ShowVolume();
}

How It Works

At the beginning of the program, we have an incomplete definition of the class Box. This is included so that the compiler will know of the existence of Box as a class when it gets to the prototype of the function Output(). Without this, the prototype would cause an error message to be generated.

The function main() now basically consists of two calls of the function Output(), the first with an object of the base class as an argument and the second with an object of the derived class. Because the parameter is a reference to the base class, Output() accepts objects of either class as an argument and the appropriate version of the virtual function Volume() is called, depending on the object that is initializing the reference.

The program produces exactly the same output as the previous example, demonstrating that the virtual function mechanism does indeed work through a reference parameter.

Pure Virtual Functions

It's possible that you'd want to include a virtual function in a base class so that it may be redefined in a derived class to suit the objects of that class, but that there is no meaningful definition you could give for the function in the base class.

For example, we could conceivably have a class Container, which could be used as a base for defining our Box class, or a Bottle class, or even a Teapot class. The Container class wouldn't have data members, but you might want to provide a virtual member function Volume() for any derived classes. Since the Container class has no data members, and therefore no dimensions, there is no sensible Volume() definition that we can write. We can still define the class, however, including the member function Volume(), as follows:

// Listing 8_10-01
class Container   // Generic base class for specific containers
{
   public:
      // Function for calculating a volume - no content
      // This is defined as a 'pure' virtual function,
      // signified by '=0'
      virtual double Volume() = 0;

      // Function to display a volume
      virtual void ShowVolume()
      {
         cout << endl
              << "Volume is " << Volume();
      }
};

The statement for the virtual function Volume() defines it as having no content by placing the equals sign and zero in the function header. This is called a pure virtual function. Any class derived from this class must either define the Volume() function or redefine it as a pure virtual function.

The class also contains the function ShowVolume(), which will display the volume of objects of derived classes. Since this is declared as virtual, it can be replaced in a derived class, but if it isn't, the base class version that you see here will be called.

Abstract Classes

A class containing a pure virtual function is called an abstract class. It's called abstract because you can't define objects of a class containing a pure virtual function. It exists only for the purpose of a base class from which you can derive other classes. If a class derived from an abstract class still defines a pure virtual function of the base as pure, it too is an abstract class.

You should not conclude, from the example of the Container class above, that an abstract class can't have data members. An abstract class can have both data members and function members. The presence of a pure virtual function is the only condition that determines that a given class is abstract. In the same vein, an abstract class can have more than one pure virtual function. In this case, a derived class must have definitions for every pure virtual function in its base, otherwise it too will be an abstract class.

Try It Out - An Abstract Class

We could implement a Can class, representing beer or cola cans perhaps, together with our original Box class. Both are derived from the Container class. The definitions of these two classes would be as follows:

// Listing 8_10-02
class Box: public Container        // Derived class
{
   public:
      // Function to show the volume of an object
      virtual void ShowVolume()
      {
         cout << endl
              << "Box usable volume is " << Volume(); 
      }

      // Function to calculate the volume of a Box object
      virtual double Volume()
      { return length*breadth*height; }

      // Constructor
      Box(double lv=1.0, double bv=1.0, double hv=1.0)
                       :length(lv), breadth(bv), height(hv){}

   protected:
      double length;
      double breadth;
      double height;
};

// Listing 8_10-03
class Can: public Container
{
   public:
      // Function to calculate the volume of a can
      virtual double Volume()
      { return 0.25*PI*diameter*diameter*height; }
      // Constructor
      Can(double hv=4.0, double dv=2.0): 
                                   height(hv), diameter(dv){}

   protected:
      double height;
      double diameter;
};

The Box class is essentially as we had it in the previous example, except that this time we have specified that it is derived from the Container class. The Volume() function is fully defined within this class (as it must be if this class is to be used to define objects). The only other option would be to specify it as a pure virtual function, since it is pure in the base class, but then we couldn't create Box objects.

The Can class also defines a Volume() function based on the formula hπr2, where h is the height and r is the radius of the cross-section of a can. This is essentially the height multiplied by the area of the base to produce the volume. The expression in the function definition assumes a global constant PI is defined, so we'll need to remember that. You will also notice that we have redefined the ShowVolume() function in the Box class, but not in the Can class. We will see what effect this has when we get some program output.

We can exercise these classes with the following main() function:

// Ex8_10.cpp
// Using an abstract class
#include <iostream>             // For stream I/O
using namespace std;

const double PI= 3.14159265;    // Global definition for PI

// Insert definition of Container class (Listing 8_10-01)
// Insert definition of Box class       (Listing 8_10-02)
// Insert definition of Can class       (Listing 8_10-03)

int main()
{
   // Pointer to abstract base class
   // initialized with address of Box object
   Container* pC1 = new Box(2.0,3.0,4.0);

   // Pointer to abstract base class
   // initialized with address of Can object
   Container* pC2 = new Can(6.5, 3.0);

   pC1->ShowVolume();          // Output the volumes of the two
   pC2->ShowVolume();          // objects pointed to
   cout << endl;

   delete pC1;                 // Now clean up the free store
   delete pC2;                 // ....
   return 0;
}

How It Works

In this program, we declare two pointers to the base class, Container. Although we can't define Container objects (because Container is an abstract class), we can still define a pointer to a Container, which we can then use to store the address of a derived class object. The pointer pC1 is assigned the address of a Box object created in the free store by the operator new. The second pointer is assigned the address of a Can object in a similar manner.

Of course, because the derived class objects were created dynamically, we need to use the operator delete to clean up the free store when we have finished with them.

The output produced by this example is as follows:

Because we have defined ShowVolume() in the Box class, the derived class version is called for the Box object. We did not define this function in the Can class, so the base class version that the Can class inherits from Container is invoked for the Can object. Since Volume() is a virtual function that is implemented in both derived classes (necessarily, because it is a pure virtual function in the base class), the call to it is resolved when the program is executed by selecting the version belonging to the class of the object being pointed to. Thus, for the pointer pC1, the version from the class Box is called and, for the pointer pC2, the version in the class Can is called. In each case, therefore, we obtain the correct result.

We could equally well have used just one pointer and assigned the address of the Can object to it (after calling the Volume() function for the Box object). A base class pointer can contain the address of any derived class object, even when several different classes are derived from the same base class, and so we can have automatic selection of the appropriate virtual function across a whole range of derived classes. Impressive stuff, isn't it?

Indirect Base Classes

At the beginning of this chapter, we said that one class's base could in turn be derived from another, 'more' base class. A small extension of the last example will provide us with an illustration of this, as well as demonstrating the use of a virtual function across a second level of inheritance.

Try It Out - More than One Level of Inheritance

All we need to do is add the class GlassBox to the classes we have in the last example. The relationship between the classes we now have is illustrated below.

The class GlassBox is derived from the Box class exactly as before, but we will omit the derived class version of ShowVolume() to show that the base class version still propagates through the derived classes. With the class hierarchy shown above, the class Container is an indirect base of the class GlassBox and a direct base of the classes Box and Can.

Our new example, with an updated function main() to use the additional class in the hierarchy, will be as follows:

// Ex8_11.cpp
// Using an abstract class with multiple levels of inheritance
#include <iostream>               // For stream I/O
using namespace std;

const double PI= 3.14159265;       // Global definition for PI

class Container   // Generic base class for specific containers
{
   public:
      // Function for calculating a volume - no content
      // This is defined as a 'pure' virtual function,
      // signified by '=0'
      virtual double Volume() = 0;

      // Function to display a volume
      virtual void ShowVolume()
      {
         cout << endl
              << "Volume is " << Volume();
      }
};

class Box: public Container        // Derived class
{
   public:
      // Function to calculate the volume of a Box object
      virtual double Volume()
      { return length*breadth*height; }

      // Constructor
      Box(double lv=1.0, double bv=1.0, double hv=1.0)
                         :length(lv), breadth(bv), height(hv){}

   protected:
      double length;
      double breadth;
      double height;
};

class Can: public Container
{
   public:
      // Function to calculate the volume of a can
      virtual double Volume()
      { return 0.25*PI*diameter*diameter*height; }

      // Constructor
      Can(double hv=4.0, double dv=2.0):
                                   height(hv), diameter(dv){}

   protected:
      double height;
      double diameter;
};

class GlassBox: public Box     // Derived class
{
   public:
      // Function to calculate volume of a GlassBox
      // allowing 15% for packing
      virtual double Volume()
      { return 0.85*length*breadth*height; }

      // Constructor
      GlassBox(double lv, double bv, double hv): Box(lv,bv,hv){}
};

int main()
{
   // Pointer to abstract base class initialized 
   // with Box object address
   Container* pC1 = new Box(2.0,3.0,4.0);
   Can myCan(6.5, 3.0);                // Define Can object
   GlassBox myGlassBox(2.0, 3.0, 4.0); // Define GlassBox object

   pC1->ShowVolume();        // Output the volume of Box
   delete pC1;               // Now clean up the free store

   pC1 = &myCan;             // Put myCan address in pointer
   pC1->ShowVolume();        // Output the volume of Can

   pC1 = &myGlassBox;        // Put myGlassBox address in pointer
   pC1->ShowVolume();        // Output the volume of GlassBox

   cout << endl;
   return 0;
}

How It Works

We have the three-level class hierarchy shown in the previous illustration, with Container as an abstract base class, because it contains the pure virtual function, Volume(). The function main() now calls the function ShowVolume() three times, using the same pointer to the base class, but with the pointer containing the address of an object of a different class each time. Since ShowVolume() is not defined in any of the derived classes we have here, the base class version is called in each instance. A separate branch from the base Container defines the derived class Can.

The example produces the following output

This shows that we execute the three different versions of the function Volume(), according to the type of object involved.

Note that we need to delete the Box object from the free store before we assign another address value to the pointer. If we don't do this, we wouldn't be able to clean up the free store, because we would have no record of the original address. This is an easy mistake to make when reassigning pointers and using the free store.

Virtual Destructors

One problem that arises when dealing with objects of derived classes by using a pointer to the base class is that the correct destructor may not be called. We can show this effect by modifying the last example.

Try It Out - Calling the Wrong Destructor

We just need to add destructors to each of the classes so that we can track which destructor is called when the objects are destroyed. Therefore, the program would be as follows:

// Ex8_12.cpp
// Destructor calls with derived classes
// using objects via a base class pointer
#include <iostream>          // For stream I/O
using namespace std;

const double PI= 3.14159265; // Global definition for PI

class Container              // Generic base class for containers
{
   public:

      // Destructor
      ~Container()
      { cout << "Container destructor called" << endl; }

      // Insert other members of Container as in Ex8_11.cpp
};

class Box: public Container        // Derived class
{
   public:

      // Destructor
      ~Box()
      { cout << "Box destructor called" << endl; }

      // Insert other members of Box as in Ex8_11.cpp
};

class Can: public Container
{
   public:

      // Destructor
      ~Can()
      { cout << "Can destructor called" << endl; }

      // Insert other members of Can as in Ex8_11.cpp
};

class GlassBox: public Box     // Derived class
{
   public:

      // Destructor
      ~GlassBox()
      { cout << "GlassBox destructor called" << endl; }

      // Insert other members of GlassBox as in Ex8_11.cpp
};

int main()
{
   // Pointer to abstract base class initialized
   // with Box object address
   Container* pC1 = new Box(2.0,3.0,4.0);
   Can myCan(6.5, 3.0);                // Define Can object
   GlassBox myGlassBox(2.0, 3.0, 4.0); // Define GlassBox object

   pC1->ShowVolume();                 // Output the volume of Box

   // Now clean up the free store
   cout << endl << "Delete Box" << endl;
   delete pC1;

   // Create GlassBox dynamically and output its volume...
   pC1 = new GlassBox(4.0, 5.0, 6.0); 
   pC1->ShowVolume();
   // ...and delete it
   cout << endl << "Delete GlassBox" << endl;
   delete pC1;                           

   pC1 = &myCan;          // Get myCan address in pointer
   pC1->ShowVolume();     // Output the volume of Can
   pC1 = &myGlassBox;     // Get myGlassBox address in pointer
   pC1->ShowVolume();     // Output the volume of GlassBox

   cout << endl;
   return 0;
}

How It Works

Apart from adding a destructor to each class, which outputs a message to the effect that it was called, the only other change is a couple of additions to the function main(). There are additional statements to create a GlassBox object dynamically, output its volume and then delete it. There is also a message displayed to indicate when the dynamically created Box object is deleted. The output generated by this example is shown below:

You can see from this that, when we delete the Box object pointed to by pC1, the destructor for the base class Container is called. Similarly, when the GlassBox object that we added is deleted, again the destructor for the base class Container is called. For the other objects, the correct destructor calls occur with the derived class constructor being called first, followed by the base class constructor. For the first GlassBox object created in a declaration, three destructors are called: first, the destructor for the derived class, followed by the direct base destructor and, finally, the indirect base destructor.

All the problems are with objects created in the free store. In both cases, the wrong destructor is called. The reason for this is that the linkage to the destructors is resolved statically, at compile time. For the automatic objects, there is no problem - the compiler knows what they are and arranges for the correct destructors to be called.

With objects created dynamically and accessed through a pointer, things are different. The only information that the compiler has when the delete operation is executed is that the pointer type is a pointer to the base class. The type of object the pointer is actually pointing to is unknown. The compiler then simply ensures that the delete operation is set up to call the base class destructor. In a real application, this can cause a lot of problems, with bits of objects left strewn around the free store and possibly more serious problems, depending on the nature of the objects involved.

The solution is simple. We need the calls to be resolved dynamically as the program is executed. We can organize this by using virtual destructors in our classes. As we said when we first discussed virtual functions, it's sufficient to declare the base class function as virtual to ensure that all functions in any derived classes with the same name, parameter list and return type are virtual as well. This applies to destructors just as it does to ordinary member functions. We need to add the keyword virtual to the definition of the destructor in the class Container, so that it becomes as follows:

class Container        // Generic base class for containers
{
   public:
      // Destructor
      virtual ~Container()
      { cout << "Container destructor called" << endl; }

      // Insert other members of Container as in Listing 8_10-01
};

Now the destructors in all the derived classes are automatically virtual, even though you don't explicitly specify them as such. Of course, you're free to specify them as virtual, if you want the code to be absolutely clear.

If you rerun the example with this modification, it will produce the following output:

As you can see, all the objects are now destroyed with a proper sequence of destructor calls. Destroying the dynamic objects produces the same sequence of destructor calls as the automatic objects of the same type in the program.

It's a good idea always to declare your base class destructor as virtual as a matter of course when using inheritance. There is a small overhead in the execution of the class destructors, but you won't notice it in the majority of circumstances. Using virtual destructors ensures that your objects will be properly destroyed and avoids potential program crashes that might otherwise occur.


© 1998 Wrox Press