
Beginning Visual C++ 2005 (2006) [eng]-1
.pdf
Class Inheritance and Virtual Functions
derived class CGlassBox is not being taken into account. You can see this from the incorrect result for the volume in the output. The volume of a CGlassBox object should definitely be less than that of a basic CBox with the same dimensions.
The reason for the incorrect output is that the call of the Volume() function in the function ShowVolume() is being set once and for all by the compiler as the version defined in the base class. ShowVolume() is a base class function and when CBox is compiled the call to Volume() is resolved at that time to the base class Volume() function; the compiler has no knowledge of any other Volume() function. This is called static resolution of the function call since 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.
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 you with a way to do this, because this whole discussion would have been futile otherwise! You need to use something called a virtual function.
What Is a Virtual Function?
A virtual function is a function in a base class that is declared using the keyword virtual. If you specify a function in a base class as virtual and there is another definition of the function in a derived class, it signals to the compiler that you don’t want static linkage for this function. What you 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 CGlassBox |
To make this example work as originally hoped, you just need to add the keyword virtual to the definitions of the Volume() function in the two classes. You can try this in a new project, Ex9_07. Here’s how the definition of CBox should be:
// Box.h in Ex9_07 #pragma once #include <iostream> using std::cout; using std::endl;
class CBox |
// Base class |
{ |
|
public:
// Function to show the volume of an object void ShowVolume() const
{
cout << endl
<< “CBox usable volume is “ << Volume();
499

Chapter 9
}
//Function to calculate the volume of a CBox object virtual double Volume() const
{ return m_Length*m_Width*m_Height; }
//Constructor
CBox(double lv = 1.0, double wv = 1.0, double hv = 1.0) :m_Length(lv), m_Width(wv), m_Height(hv) {}
protected:
double m_Length; double m_Width; double m_Height;
};
The GlassBox.h header file contents should be:
// GlassBox.h in Ex9_07 #pragma once
#include “Box.h”
class CGlassBox: public CBox |
// Derived class |
{ |
|
public: |
|
//Function to calculate volume of a CGlassBox
//allowing 15% for packing
virtual double Volume() const
{ return 0.85*m_Length*m_Width*m_Height; }
// Constructor
CGlassBox(double lv, double wv, double hv): CBox(lv, wv, hv){}
};
The Ex9_07.cpp file version of main() is the same as for the previous example:
//Ex9_07.cpp (the same as Ex9_06.cpp)
//Using a virtual function
#include <iostream>
#include “GlassBox.h” // For CBox and CGlassBox using std::cout;
using std::endl;
int main() |
|
{ |
|
CBox myBox(2.0, 3.0, 4.0); |
// Declare a base box |
CGlassBox myGlassBox(2.0, 3.0, 4.0); // Declare derived box - same size
myBox.ShowVolume(); |
// Display volume |
of base box |
myGlassBox.ShowVolume(); |
// Display volume |
of derived box |
cout << endl; |
|
|
return 0; |
|
|
} |
|
|
500

Class Inheritance and Virtual Functions
How It Works
If you run this version of the program with just the little word virtual added to the definitions of Volume(), it produces this output:
CBox usable volume is 24
CBox usable volume is 20.4
This is now clearly doing what you wanted in the first place. The first call to the function ShowVolume() with the CBox object myBox calls the CBox class version of Volume(). The second call with the CGlassBox object myGlassBox calls the version defined in the derived class.
Note that although you 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 because it makes it clear to anyone reading the derived class definition that they are virtual functions and that they are selected dynamically.
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, and if the base class function is const, the derived class function must be, too. If you try to use different parameters or return types, or declare one as const and the other not, the virtual function mechanism won’t work. The function operates 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 produces different effects depending on the kind of object for which it is being called.
Note that the Volume() function in the derived CGlassBox 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
CBox::Volume().
Using Pointers to Class Objects
Using pointers with objects of a base class and of a derived class is a, 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. You 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. You can see how this works more clearly by looking at an example.
Try It Out Pointers to Base and Derived Classes
You’ll 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. Create the Ex9_08 project with Box.h and GlassBox.h header files the same as in the previous example. You can copy the Box.h and Glassbox.h files from the Ex9_07 project to this project folder. Adding an existing file to a project is quite easy; you
501

Chapter 9
right-click Ex9_08 in the Solution Explorer tab, select Add > New Item from the pop-up menu; then select a header file to add it to the project. When you have added the headers, modify Ex9_08.cpp to the following:
//Ex9_08.cpp
//Using a base class pointer to call a virtual function #include <iostream>
#include “GlassBox.h” |
// For CBox and CGlassBox |
using std::cout; |
|
using std::endl; |
|
int main() |
|
{ |
|
CBox myBox(2.0, 3.0, 4.0); |
// Declare a base box |
CGlassBox myGlassBox(2.0, 3.0, 4.0); // Declare derived box of same size
CBox* 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 Ex9_07.cpp, but the function main() has been altered to use a pointer to call the function ShowVolume(). Because you are using a pointer, you 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:
CBox usable volume is 24
CBox usable volume is 20.4
This is exactly the same as that from the previous example where you used explicit objects in the function call.
You 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 Figure 9-5.
This means that, even when you 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 ensures 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.
502

Class Inheritance and Virtual Functions
pBox->ShowVolume()
Pointer this is set to pBox
|
|
|
|
|
|
|
|
|
classCBox |
|
|
|
|
|
void ShowVolume() const |
|
virtual double Volume () const |
||
|
{...} |
|||
{ |
|
|
||
|
|
|
|
|
cout << endl |
|
|
|
|
|
|
|
||
<< “CBox usable volume is ” |
pBox pointing |
to |
||
|
|
CBox object |
|
|
<< Volume(); |
|
|
|
|
} |
|
pBox pointing |
to |
|
|
|
CGlassBox object |
classCGlassBox
virtual double Volume () const {...}
Figure 9-5
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 is selected automatically. We could see 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():
//Ex9_09.cpp
//Using a reference to call a virtual function #include <iostream>
#include “GlassBox.h” |
// For CBox and CGlassBox |
using std::cout; |
|
using std::endl; |
|
|
|
void Output(const CBox& aBox); |
// Prototype of function |
int main() |
|
{ |
|
CBox myBox(2.0, 3.0, 4.0); |
// Declare a base box |
503

Chapter 9
CGlassBox myGlassBox(2.0, 3.0, 4.0); // Declare derived box of same size
Output(myBox); |
// Output volume of base class object |
Output(myGlassBox); |
// Output volume of derived class object |
cout << endl; |
|
return 0; |
|
} |
|
|
|
void Output(const CBox& aBox) |
|
{ |
|
aBox.ShowVolume(); |
|
} |
|
|
|
Box.h and GlassBox.h for this example have the same contents as the previous example.
How It Works
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.
Incomplete Class Definitions
At the beginning of the previous example, you have the prototype declaration for the Output() function. To process this declaration the compiler needs to have access to the definition of the CBox class because the parameter is of type CBox&. In this case the definition of the CBox class is available at this point because you have an #include directive for GlassBox.h that has its own #include directive for Box.h.
However, there may be situations where you have such a declaration and the class definition cannot be included in this way, in which case you would need some other way to at least identify that the name CBox refers to a class type. In this situation you could provide an incomplete definition of the class CBox preceding the prototype of the output function. The statement that provides an incomplete definition of the CBox class is simply:
class CBox;
The statement just identifies that the name CBox refers to a class that is not defined at this point, but this is sufficient for the compiler know of that CBox is the name of a class, and this allows it to process the prototype of the function Output(). Without some indication that CBox is a class, the prototype causes an error message to be generated.
504

Class Inheritance and Virtual Functions
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, you could conceivably have a class CContainer, which could be used as a base for defining the CBox class, or a CBottle class, or even a CTeapot class. The CContainer class wouldn’t have data members, but you might want to provide a virtual member function Volume() for any derived classes. Because the CContainer class has no data members, and therefore no dimensions, there is no sensible definition that you can write for the Volume() function. You can still define the class, however, including the member function Volume(), as follows:
// Container.h for Ex9_10 #pragma once
#include <iostream> using std::cout; using std::endl;
class CContainer |
// 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() const = 0;
//Function to display a volume
virtual void ShowVolume() const
{
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. Because you have declared Volume() as const, its implementation in any derived class must also be const. Remember that const and non-const varieties of a function with the same name and parameter list are different functions. In other words you can overload a function using const.
The class also contains the function ShowVolume(), which displays the volume of objects of derived classes. Because 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 is 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 defining classes that are derived from it. 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.
505

Chapter 9
You should not conclude, from the previous example of the CContainer class, 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. If you forget to make the derived class version of the Volume() function const, the derived class will still be abstract because it contains the pure virtual Volume() member function that is const, as well as the non-const Volume() function that you have defined.
Try It Out |
An Abstract Class |
You could implement a CCan class, representing beer or cola cans perhaps, together with the original CBox class and derive both are derived from the CContainer class that you defined in the previous section.. The definition of the CBox class as a subclass of CContainer is as follows:
// Box.h for Ex9_10 |
|
#pragma once |
|
#include “Container.h” |
// For CContainer definition |
#include <iostream> |
|
using std::cout; |
|
using std::endl; |
|
|
|
class CBox: public CContainer |
// Derived class |
{ |
|
public: |
|
//Function to show the volume of an object virtual void ShowVolume() const
{
cout << endl
<<“CBox usable volume is “ << Volume();
}
//Function to calculate the volume of a CBox object virtual double Volume() const
{ return m_Length*m_Width*m_Height; }
//Constructor
CBox(double lv = 1.0, double wv = 1.0, double hv = 1.0) :m_Length(lv), m_Width(wv), m_Height(hv){}
protected:
double m_Length; double m_Width; double m_Height;
};
The unshaded lines are the same as in the previous version of the CBox class. The CBox class is essentially as we had it in the previous example, except this time you have specified that it is derived from the CContainer 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 CBox objects.
506

Class Inheritance and Virtual Functions
You could define the CCan class in the Can.h header file like this:
// Can.h for Ex9_10 |
|
#pragma once |
|
#include “Container.h” |
// For CContainer definition |
extern const double PI; |
// PI is defined elsewhere |
class CCan: public CContainer |
|
{ |
|
public: |
|
//Function to calculate the volume of a can virtual double Volume() const
{ return 0.25*PI*m_Diameter*m_Diameter*m_Height; }
//Constructor
CCan(double hv = 4.0, double dv = 2.0): m_Height(hv), m_Diameter(dv){}
protected:
double m_Height; double m_Diameter;
};
The CCan class also defines a Volume() function based on the formula hπr2, where h is the height of a can and r is the radius of the cross-section of a can. The volume is calculated as the height multiplied by the area of the base. The expression in the function definition assumes a global constant PI is defined, so we have the extern statement indicating that PI is a global variable of type const double that is defined elsewhere — in this program it is defined in the Ex9_10.cpp file. Also notice that we redefined the ShowVolume() function in the CBox class, but not in the CCan class. You can see what effect this has when we get some program output.
You can exercise these classes with the following source file containing the main() function:
//Ex9_10.cpp
//Using an abstract class #include “Box.h”
#include “Can.h” #include <iostream> using std::cout; using std::endl;
//For CBox and CContainer
//For CCan (and CContainer)
//For stream I/O
const double PI= 3.14159265; |
// Global definition for PI |
int main(void)
{
//Pointer to abstract base class
//initialized with address of CBox object CContainer* pC1 = new CBox(2.0, 3.0, 4.0);
//Pointer to abstract base class
//initialized with address of CCan object CContainer* pC2 = new CCan(6.5, 3.0);
pC1->ShowVolume(); |
// |
Output the volumes of the two |
pC2->ShowVolume(); |
// |
objects pointed to |
507

Chapter 9
cout << endl; |
|
delete pC1; |
// Now clean up the free store |
delete pC2; |
// .... |
return 0; |
|
} |
|
|
|
How It Works
In this program, you declare two pointers to the base class, CContainer. Although you can’t define CContainer objects (because CContainer is an abstract class), you can still define a pointer to a CContainer, which you can then use to store the address of a derived class object; in fact you can use it to store the address of any object whose type is a direct or indirect subclass of CContainer. The pointer pC1 is assigned the address of a CBox object created in the free store by the operator new. The second pointer is assigned the address of a CCan object in a similar manner.
Of course, because the derived class objects were created dynamically, you must use the delete operator to clean up the free store when you have finished with them.
The output produced by this example is as follows:
CBox usable volume is 24
Volume is 45.9458
Because you have defined ShowVolume() in the CBox class, the derived class version of the function is called for the CBox object. You did not define this function in the CCan class, so the base class version that the CCan class inherits is invoked for the CCan object. Because Volume() is a virtual function 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 CBox is called and, for the pointer pC2, the version in the class CCan is called. In each case, therefore, you obtain the correct result.
You could equally well have used just one pointer and assigned the address of the CCan object to it (after calling the Volume() function for the CBox 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 you 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, I said that a base class for a subclass could in turn be derived from another, ‘more’ base class. A small extension of the last example provides you with an illustration of this, as well as demonstrates the use of a virtual function across a second level of inheritance.
Try It Out |
More than One Level of Inheritance |
All you need to do is add the class CGlassBox to the classes you have from the previous example. The relationship between the classes you now have is illustrated in Figure 9-6.
508