Tải bản đầy đủ (.pdf) (139 trang)

Ivor Horton’s BeginningVisual C++ 2008 phần 5 ppt

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (1.25 MB, 139 trang )

How It Works
In this example you calculate the volumes of the two CCandyBox objects by invoking the Volume()
function that is a member of the derived class. This function accesses the inherited members m_Length,
m_Width, and m_Height to produce the result. The members are declared as protected in the base
class and remain
protected in the derived class. The program produces the output shown as follows:
CBox constructor called
CCandyBox constructor1 called
CBox constructor called
CCandyBox constructor2 called
myCandyBox volume is 1
myToffeeBox volume is 24
CCandyBox destructor called
CBox destructor called
CCandyBox destructor called
CBox destructor called
The output shows that the volume is being calculated properly for both CCandyBox objects. The first
object has the default dimensions produced by calling the default
CBox constructor, so the volume is 1,
and the second object has the dimensions defined as initial values in its declaration.
The output also shows the sequence of constructor and destructor calls, and you can see how each derived
class object is destroyed in two steps.
Destructors for a derived class object are called in the reverse order to the constructors for the object.
This is a general rule that always applies. Constructors are invoked starting with the base class con-
structor and then the derived class constructor, whereas the destructor for the derived class is called
first when an object is destroyed, followed by the base class destructor.
You can demonstrate that the
protected members of the base class remain protected in the derived
class by uncommenting the statement preceding the
return statement in the function main(). If you
do this, you get the following error message from the compiler,


error C2248: ‘m_Length’: cannot access protected member declared in class ‘CBox’
which indicates quite clearly that the member m_Length is inaccessible.
The Access Level of Inherited Class Members
You know that if you have no access specifier for the base class in the definition of a derived class, the
default specification is
private. This has the effect of causing the inherited public and protected
members of the base class to become private in the derived class. The private members of the base
class remain
private to the base and therefore inaccessible to member functions of the derived class.
In fact, they remain
private to the base class regardless of how the base class is specified in the derived
class definition.
You have also used
public as the specifier for a base class. This leaves the members of the base class with
the same access level in the derived class as they had in the base, so
public members remain public and
protected members remain protected.
522
Chapter 9: Class Inheritance and Virtual Functions
25905c09.qxd:WroxPro 2/21/08 8:54 AM Page 522
The last possibility is that you declare a base class as protected. This has the effect of making the inher-
ited
public members of the base protected in the derived class. The protected (and private) inherited
members retain their original access level in the derived class. This is summarized in Figure 9-3.
Figure 9-3
This may look a little complicated, but you can reduce it to the following three points about the inherited
members of a derived class:
❑ Members of a base class that are declared as
private are never accessible in a derived class.
❑ Defining a base class as

public doesn’t change the access level of its members in the derived
class.
❑ Defining a base class as
protected changes its public members to protected in the derived
class.
Being able to change the access level of inherited members in a derived class gives you a degree of flexi-
bility, but don’t forget that you cannot relax the level specified in the base class; you can only make the
access level more stringent. This suggests that your base classes need to have
public members if you
want to be able to vary the access level in derived classes. This may seem to run contrary to the idea of
encapsulating data in a class in order to protect it from unauthorized access, but, as you’ll see, it is often
the case that you define base classes in such a manner that their only purpose is to act as a base for other
classes, and they aren’t intended to be used for instantiating objects in their own right.
inherited as
inherited as
public:
public:
protected:
protected:
protected:
protected:
private:
private:
private:
inherited as
No access - ever.
class CBox
class CCBox:private CBox
class CBBox:protected CBox
class CABox:public CBox

inherited as
inherited as
inherited as
•••
•••
•••
•••
•••
•••
•••
•••
•••
}
{
{
}
{
}
{
}
523
Chapter 9: Class Inheritance and Virtual Functions
25905c09.qxd:WroxPro 2/21/08 8:54 AM Page 523
The Copy Constructor in a Derived Class
Remember that the copy constructor is called automatically when you declare an object that is initialized
with an object of the same class. Look at these statements:
CBox myBox(2.0, 3.0, 4.0); // Calls constructor
CBox copyBox(myBox); // Calls copy constructor
The first statement calls the constructor that accepts three arguments of type double, and the second
calls the copy constructor. If you don’t supply your own copy constructor, the compiler supplies one

that copies the initializing object member by member to the corresponding members of the new object.
So that you can see what is going on during execution, you can add your own version of a copy con-
structor to the class
CBox. You can then use this class as a base for defining the CCandyBox class.
// Box.h in Ex9_05
#pragma once
#include <iostream>
using std::cout;
using std::endl;
class CBox // Base class definition
{
public:
// Base class constructor
CBox(double lv = 1.0, double wv = 1.0, double hv = 1.0):
m_Length(lv), m_Width(wv), m_Height(hv)
{ cout << endl << “CBox constructor called”; }
// Copy constructor
CBox(const CBox& initB)
{
cout << endl << “CBox copy constructor called”;
m_Length = initB.m_Length;
m_Width = initB.m_Width;
m_Height = initB.m_Height;
}
// CBox destructor - just to track calls
~CBox()
{ cout << “CBox destructor called” << endl; }
protected:
double m_Length;
double m_Width;

double m_Height;
};
Also recall that the copy constructor must have its parameter specified as a reference to avoid an infinite
number of calls to itself, which would otherwise result from the need to copy an argument that is trans-
ferred by value. When the copy constructor in our example is invoked, it outputs a message to the screen,
so you’ll be able to see from the output when this is happening.
524
Chapter 9: Class Inheritance and Virtual Functions
25905c09.qxd:WroxPro 2/21/08 8:54 AM Page 524
We will use the version of CCandyBox class from Ex9_04.cpp, shown again here:
// CandyBox.h in Ex9_05
#pragma once
#include “Box.h”
#include <iostream>
using std::cout;
using std::endl;
class CCandyBox: public CBox
{
public:
char* m_Contents;
// Derived class function to calculate volume
double Volume() const
{ return m_Length*m_Width*m_Height; }
// Constructor to set dimensions and contents
// with explicit call of CBox constructor
CCandyBox(double lv, double wv, double hv, char* str = “Candy”)
:CBox(lv, wv, hv) // Constructor
{
cout << endl <<”CCandyBox constructor2 called”;
m_Contents = new char[ strlen(str) + 1 ];

strcpy_s(m_Contents, strlen(str) + 1, str);
}
// Constructor to set contents
// calls default CBox constructor automatically
CCandyBox(char* str = “Candy”) // Constructor
{
cout << endl << “CCandyBox constructor1 called”;
m_Contents = new char[ strlen(str) + 1 ];
strcpy_s(m_Contents, strlen(str) + 1, str);
}
~CCandyBox() // Destructor
{
cout << “CCandyBox destructor called” << endl;
delete[] m_Contents;
}
};
This doesn’t have a copy constructor added yet, so you’ll be relying on the compiler-generated version.
Try It Out The Copy Constructor in Derived Classes
You can exercise the copy constructor that you have just defined with the following example:
// Ex9_05.cpp
// Using a derived class copy constructor
#include <iostream> // For stream I/O
#include <cstring> // For strlen() and strcpy()
#include “CandyBox.h” // For CBox and CCandyBox
525
Chapter 9: Class Inheritance and Virtual Functions
25905c09.qxd:WroxPro 2/21/08 8:54 AM Page 525
using std::cout;
using std::endl;
int main()

{
CCandyBox chocBox(2.0, 3.0, 4.0, “Chockies”); // Declare and initialize
CCandyBox chocolateBox(chocBox); // Use copy constructor
cout << endl
<< “Volume of chocBox is “ << chocBox.Volume()
<< endl
<< “Volume of chocolateBox is “ << chocolateBox.Volume()
<< endl;
return 0;
}
How It Works (or Why It Doesn’t)
When you run the Debug version of this example, in addition to the expected output, you’ll see the dialog
shown in Figure 9-4 displayed.
Click
Abort to clear the dialog box and you’ll see the output in the console window that you might expect.
The output shows that the compiler-generated copy constructor for the derived class automatically called
the copy constructor for the base class.
However, as you’ve probably realized, all is not as it should be. In this particular case, the compiler-
generated copy constructor causes problems because the memory pointed to by the
m_Contents mem-
ber of the derived class in the second object declared points to the same memory as the one in the first
object. When one object is destroyed (when it goes out of scope at the end of
main()), it releases the
memory occupied by the text. When the second object is destroyed, the destructor attempts to release
some memory that has already been freed by the destructor call for the previous object — and that’s
the reason for the error message in the dialog box.
The way to fix this is to supply a copy constructor for the derived class that allocates some additional
memory for the new object.
Figure 9-4
526

Chapter 9: Class Inheritance and Virtual Functions
25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 526
Try It Out Fixing the Copy Constructor Problem
You can do this by adding the following code for the copy constructor to the public section of the derived
CCandyBox class in Ex9_05:
// Derived class copy constructor
CCandyBox(const CCandyBox& initCB)
{
cout << endl << “CCandyBox copy constructor called”;
// Get new memory
m_Contents = new char[ strlen(initCB.m_Contents) + 1 ];
// Copy string
strcpy_s(m_Contents, strlen(initCB.m_Contents) + 1, initCB.m_Contents);
}
You can now run this new version of the last example with the same function main() to see how the new
copy constructor works.
How It Works
Now when you run the example, it behaves better and produces the following output:
CBox constructor called
CCandyBox constructor2 called
CBox constructor called
CCandyBox copy constructor called
Volume of chocBox is 24
Volume of chocolateBox is 1
CCandyBox destructor called
CBox destructor called
CCandyBox destructor called
CBox destructor called
However, there is still something wrong. The third line of output shows that the default constructor for the
CBox part of the object chocolateBox is called, rather than the copy constructor. As a consequence, the

object has the default dimensions rather than the dimensions of the initializing object, so the volume is
incorrect. The reason for this is that when you write a constructor for an object of a derived class, you are
responsible for ensuring that the members of the derived class object are properly initialized. This includes
the inherited members.
The fix for this is to call the copy constructor for the base part of the class in the initialization list for the
copy constructor for the
CCandyBox class. The copy constructor then becomes:
// Derived class copy constructor
CCandyBox(const CCandyBox& initCB): CBox(initCB)
{
cout << endl << “CCandyBox copy constructor called”;
// Get new memory
527
Chapter 9: Class Inheritance and Virtual Functions
25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 527
m_Contents = new char[ strlen(initCB.m_Contents) + 1 ];
// Copy string
strcpy_s(m_Contents, strlen(initCB.m_Contents) + 1, initCB.m_Contents);
}
Now the CBox class copy constructor is called with the initCB object. Only the base part of the object is
passed to it, so everything works out. If you modify the last example by adding the base copy constructor
call, the output is as follows:
CBox constructor called
CCandyBox constructor2 called
CBox copy constructor called
CCandyBox copy constructor called
Volume of chocBox is 24
Volume of chocolateBox is 24
CCandyBox destructor called
CBox destructor called

CCandyBox destructor called
CBox destructor called
The output shows that all the constructors and destructors are called in the correct sequence and the
copy constructor for the
CBox part of chocolateBox is called before the CCandyBox copy constructor.
The volume of the object
chocolateBox of the derived class is now the same as that of its initializing
object, which is as it should be.
You have, therefore, another golden rule to remember:
If you write any kind of constructor for a derived class, you are responsible for the initialization of all
members of the derived class object, including all its inherited members.
Class Members as Friends
You saw in Chapter 7 how a function can be declared as a friend of a class. This gives the friend func-
tion the privilege of free access to any of the class members. Of course, there is no reason why a
friend
function cannot be a member of another class.
Suppose you define a
CBottle class to represent a bottle:
class CBottle
{
public:
CBottle(double height, double diameter)
{
m_Height = height;
m_Diameter = diameter;
}
528
Chapter 9: Class Inheritance and Virtual Functions
25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 528
private:

double m_Height; // Bottle height
double m_Diameter; // Bottle diameter
};
You now need a class to represent the packaging for a dozen bottles that automatically has custom
dimensions to accommodate a particular kind of bottle. You could define this as:
class CCarton
{
public:
CCarton(const CBottle& aBottle)
{
m_Height = aBottle.m_Height; // Bottle height
m_Length = 4.0*aBottle.m_Diameter; // Four rows of
m_Width = 3.0*aBottle.m_Diameter; // three bottles
}
private:
double m_Length; // Carton length
double m_Width; // Carton width
double m_Height; // Carton height
};
The constructor here sets the height to be the same as that of the bottle it is to accommodate, and the
length and width are set based on the diameter of the bottle so that twelve fit in the box.
As you know by now, this won’t work. The data members of the
CBottle class are private, so the
CCarton constructor cannot access them. As you also know, a friend declaration in the CBottle class
fixes it:
class CBottle
{
public:
CBottle(double height, double diameter)
{

m_Height = height;
m_Diameter = diameter;
}
private:
double m_Height; // Bottle height
double m_Diameter; // Bottle diameter
// Let the carton constructor in
friend CCarton::CCarton(const CBottle& aBottle);
};
The only difference between the friend declaration here and what you saw in Chapter 7 is that you
must put the class name and the scope resolution operator with the
friend function name to identify it.
For this to compile correctly, the compiler needs to have information about the
CCarton class construc-
tor, so you would need to put an
#include statement for the header file containing the CCarton class
definition before the definition of the
CBottle class.
529
Chapter 9: Class Inheritance and Virtual Functions
25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 529
Friend Classes
You can also allow all the function members of one class to have access to all the data members of another
by declaring it as a friend class. You could define the
CCarton class as a friend of the CBottle class by
adding a friend declaration within the
CBottle class definition:
friend CCarton;
With this declaration in the CBottle class, all function members of the CCarton class now have free
access to all the data members of the

CBottle class.
Limitations on Class Friendship
Class friendship is not reciprocated. Making the CCarton class a friend of the CBottle class does not
mean that the
CBottle class is a friend of the CCarton class. If you want this to be so, you must add a
friend declaration for the CBottle class to the CCarton class.
Class friendship is also not inherited. If you define another class with
CBottle as a base, members of
the
CCarton class will not have access to its data members, not even those inherited from CBottle.
Virtual Functions
Look more closely at the behavior of inherited member functions and their relationship with derived
class member functions. You could add a function to the
CBox class to output the volume of a CBox
object. The simplified class then becomes:
// Box.h in Ex9_06
#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();
}
// Function to calculate the volume of a CBox object

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) {}
530
Chapter 9: Class Inheritance and Virtual Functions
25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 530
protected:
double m_Length;
double m_Width;
double m_Height;
};
Now you can output the usable volume of a CBox object just by calling the ShowVolume() function for
any object for which you 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 spec-
ified as
protected, so they are accessible to the member functions of any derived class.
Suppose you want to derive a class for a different kind of box called
CGlassBox, 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
CBox object. You therefore need a different Volume() function to account for
this, so you add it to the derived class:
// GlassBox.h in Ex9_06
#pragma once
#include “Box.h”
class CGlassBox: public CBox // Derived class
{
public:

// Function to calculate volume of a CGlassBox
// allowing 15% for packing
double Volume() const
{ return 0.85*m_Length*m_Width*m_Height; }
// Constructor
CGlassBox(double lv, double wv, double hv): CBox(lv, wv, hv){}
};
There could conceivably be other additional members of the derived class, but we’ll keep it simple and con-
centrate 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. You have included a new version of the
Volume() function to replace the version from
the base class, the idea being that you can get the inherited function
ShowVolume() to call the derived class
version of the member function
Volume() when you call it for an object of the class CGlassBox.
Try It Out Using an Inherited Function
Now see how your derived class works in practice. You 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 is as follows:
// Ex9_06.cpp
// Behavior of inherited functions in a derived class
#include <iostream>
#include “GlassBox.h” // For CBox and CGlassBox
using std::cout;
using std::endl;
531
Chapter 9: Class Inheritance and Virtual Functions
25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 531

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;
}
How It Works
If you run this example, it produces the following output:
CBox usable volume is 24
CBox usable volume is 24
This isn’t only dull and repetitive, but it’s also disastrous. It isn’t working the way you 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
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,
532
Chapter 9: Class Inheritance and Virtual Functions
25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 532
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 defini-
tions 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();
}
// 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
533
Chapter 9: Class Inheritance and Virtual Functions
25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 533
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;
}
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 suf-
ficient. 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
534
Chapter 9: Class Inheritance and Virtual Functions
25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 534
the other not, the virtual function mechanism won’t work. The function operates with static linkage estab-
lished 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 an 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 proj-
ect is quite easy; you 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
535

Chapter 9: Class Inheritance and Virtual Functions
25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 535
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 mem-
ber 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 func-
tion 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.
Figure 9-5

classCBox
virtual double Volume () const
{ }
pBox->ShowVolume()
void ShowVolume() const
{
cout << endl

<< “CBox usable volume is ”

<< Volume();
}
Pointer this
is set to pBox
classCGlassBox
virtual double Volume () const
{ }
pBox pointing to
CBox object
pBox pointing to
CGlassBox object
536
Chapter 9: Class Inheritance and Virtual Functions
25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 536
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.
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
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
537
Chapter 9: Class Inheritance and Virtual Functions
25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 537
the appropriate version of the virtual function Volume() is called, depending on the object that is initial-
izing 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 preced-
ing 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.
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:
538
Chapter 9: Class Inheritance and Virtual Functions
25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 538
// 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 defin-
ing 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.
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 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;
539
Chapter 9: Class Inheritance and Virtual Functions
25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 539
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 essen-
tially 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 func-
tion, since it is pure in the base class, but then we couldn’t create
CBox objects.
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;
};
540
Chapter 9: Class Inheritance and Virtual Functions
25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 540
The CCan class also defines a Volume() function based on the formula hπr
2
, 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” // For CBox and CContainer
#include “Can.h” // For CCan (and CContainer)

#include <iostream> // For stream I/O
using std::cout;
using std::endl;
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
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. You learned about the
delete operator
back in Chapter 4.
541
Chapter 9: Class Inheritance and Virtual Functions
25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 541
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.
The class
CGlassBox is derived from the CBox class exactly as before, but we omit the derived class ver-
sion of
ShowVolume() to show that the base class version still propagates through the derived classes.
With the class hierarchy shown above, the class
CContainer is an indirect base of the class CGlassBox,
and a direct base of the classes
CBox and CCan.
The
GlassBox.h header file for the example contains:
// GlassBox.h for Ex9_11
#pragma once
#include “Box.h” // For CBox
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; }
542
Chapter 9: Class Inheritance and Virtual Functions
25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 542
// Constructor
CGlassBox(double lv, double wv, double hv): CBox(lv, wv, hv){}
};

Figure 9-6
The
Container.h, Can.h, and Box.h header files contain the same code as those in the previous example,
Ex9_10.
The source file for new example, with an updated function
main() to use the additional class in the hier-
archy, is as follows:
// Ex9_11.cpp
// Using an abstract class with multiple levels of inheritance
#include “Box.h” // For CBox and CContainer
#include “Can.h” // For CCan (and CContainer)
#include “GlassBox.h” // For CGlassBox (and CBox and CContainer)
#include <iostream> // For stream I/O
using std::cout;
using std::endl;
const double PI = 3.14159265; // Global definition for PI
int main()
{
// Pointer to abstract base class initialized with CBox object address
class CContainer Direct base of CBox
Indirect base of CGlassBox
Direct base of CCan
Direct base of CGlassBox
More General
class CBox
class CGlassBox
class CCan
More Specialized
543
Chapter 9: Class Inheritance and Virtual Functions

25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 543
CContainer* pC1 = new CBox(2.0, 3.0, 4.0);
CCan myCan(6.5, 3.0); // Define CCan object
CGlassBox myGlassBox(2.0, 3.0, 4.0); // Define CGlassBox object
pC1->ShowVolume(); // Output the volume of CBox
delete pC1; // Now clean up the free store
// initialized with address of CCan object
pC1 = &myCan; // Put myCan address in pointer
pC1->ShowVolume(); // Output the volume of CCan
pC1 = &myGlassBox; // Put myGlassBox address in pointer
pC1->ShowVolume(); // Output the volume of CGlassBox
cout << endl;
return 0;
}
How It Works
You have the three-level class hierarchy shown in Figure 9-6 with CContainer as an abstract base
class because it contains the pure virtual function,
Volume(). The main()function now calls the
ShowVolume()function 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. Because
ShowVolume() is not
defined in any of the derived classes you have here, the base class version is called in each instance.
A separate branch from the base
CContainer defines the derived class CCan.
The example produces this output:
CBox usable volume is 24
Volume is 45.9458
CBox usable volume is 20.4
The output shows that one of the three different versions of the function Volume() is selected for execu-
tion according to the type of object involved.

Note that you must delete the
CBox object from the free store before you assign another address value to
the pointer. If you don’t do this, you won’t be able to clean up the free store, because you would have no
record of the address of the original object. 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 using a pointer to the base class is
that the correct destructor may not be called. You can see this effect by modifying the last example.
544
Chapter 9: Class Inheritance and Virtual Functions
25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 544
Try It Out Calling the Wrong Destructor
You just need to add a destructor to each of the classes in the example that outputs a message so that
you can track which destructor is called when the objects are destroyed. The
Container.h file for this
example is:
// Container.h for Ex9_12
#pragma once
#include <iostream>
using std::cout;
using std::endl;
class CContainer // Generic base class for specific containers
{
public:
// Destructor
~CContainer()
{ cout << “CContainer destructor called” << endl; }
// 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 contents of Can.h in the example is:
// Can.h for Ex9_12
#pragma once
#include “Container.h” // For CContainer definition
extern const double PI;
class CCan: public CContainer
{
public:
// Destructor
~CCan()
{ cout << “CCan destructor called” << endl; }
// Function to calculate the volume of a can
virtual double Volume() const
{ return 0.25*PI*m_Diameter*m_Diameter*m_Height; }
// Constructor
545
Chapter 9: Class Inheritance and Virtual Functions
25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 545
CCan(double hv = 4.0, double dv = 2.0): m_Height(hv), m_Diameter(dv){}
protected:
double m_Height;
double m_Diameter;
};

The contents of Box.h should be:
// Box.h for Ex9_12
#pragma once
#include “Container.h” // For CContainer definition
#include <iostream>
using std::cout;
using std::endl;
class CBox: public CContainer // Derived class
{
public:
// Destructor
~CBox()
{ cout << “CBox destructor called” << endl; }
// 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 GlassBox.h header file should contain:
// GlassBox.h for Ex9_12
#pragma once
#include “Box.h” // For CBox
class CGlassBox: public CBox // Derived class
546
Chapter 9: Class Inheritance and Virtual Functions
25905c09.qxd:WroxPro 2/21/08 8:55 AM Page 546

×