Even if there is no derived class and there is only one virtual member function, but that function does not have a definition, this kind of message still occurs.. By making the member fu
Trang 1Self-Test Exercises
1 Explain the difference among the terms virtual function, late binding, and polymorphism.
2 Suppose you modify the definitions of the class Sale (Display 15.1) by deleting the reserved word virtual How would that change the output of the program in Display 15.5?
O MITTING THE D EFINITION OF A V IRTUAL M EMBER F UNCTION
It is wise to develop incrementally This means code a little, then test a little, then code a little more and test a little more, and so forth However, if you try to compile classes with virtual member functions but do not implement each member, you may run into some very-hard-to-understand error messages, even if you do not call the undefined member functions!
If any virtual member functions are not implemented before compiling, the compilation fails with error messages similar to this:
Undefined reference to Class_Name virtual table.
Even if there is no derived class and there is only one virtual member function, but that function does not have a definition, this kind of message still occurs.
What makes the error messages very hard to decipher is that without definitions for the functions declared virtual , there will be further error messages complaining about an undefined refer-ence to default constructors, even if these constructors really are already defined.
Of course, you may use some trivial definition for a virtual function until you are ready to define the “real” version of the function.
This caution does not apply to pure virtual functions, which we discuss in the next section As you will see, pure virtual functions are not supposed to have a definition.
You can encounter situations in which you want to have a class to use as a base class for
a number of other classes, but you do not have any meaningful definition to give to one
or more of its member functions When we introduced virtual functions we discussed one such scenario Let’s review it now
Suppose you are designing software for a graphics package that has classes for several kinds of figures, such as rectangles, circles, ovals, and so forth Each figure might be an object of a different class, such as the Rectangle class or the Circle class In a well-designed programming project, all of these classes would probably be descendants of a single parent class called, for example, Figure Now, suppose you want a function to draw a figure on the screen To draw a circle, you need different instructions from those you need to draw a rectangle So, each class needs to have a different function to draw
Trang 2638 Polymorphism and Virtual Functions
Example
its kind of figure If r is a Rectangle object and c is a Circle object, then r.draw( )
and c.draw( ) can be functions implemented with different code
The parent class Figure may have a function called center that moves a figure to the center of the screen by erasing it and then redrawing it in the center of the screen The function Figure::center might use the function draw to redraw the figure in the center of the screen By making the member function draw a virtual function, you can write the code for the member function Figure::center in the class Figure and know that when it is used for a derived class, say Circle, the definition of draw in the class
Circle will be the definition used You never plan to create an object of type Figure You only intend to create objects of the derived classes, such as Circle and Rectangle
So, the definition that you give to Figure::draw will never be used However, based only on what we covered so far, you would still need to give a definition for Fig-ure::draw, even though it could be trivial
If you make the member function Figure::draw a pure virtual function, then you
do not need to give any definition to that member function The way to make a mem-ber function into a pure virtual function is to mark it as virtual and to add the annota-tion = 0 to the member function declaration, as in the following example:
virtual void draw( ) = 0;
Any kind of member can be made a pure virtual function It need not be a void func-tion with no parameters as in our example
A class with one or more pure virtual functions is called an abstract class An
abstract class can only be used as a base class to derive other classes You cannot create objects of an abstract class, since it is not a complete class definition An abstract class is
a partial class definition because it can contain other member functions that are not pure virtual functions An abstract class is also a type, so you can write code with parameters of the abstract class type and it will apply to all objects of classes that are descendants of the abstract class
If you derive a class from an abstract class, the derived class will itself be an abstract class unless you provide definitions for all the inherited pure virtual functions (and also
do not introduce any new pure virtual functions) If you do provide definitions for all the inherited pure virtual functions (and also do not introduce any new pure virtual functions), the resulting class is not an abstract class, which means you can create objects of the class
A N A BSTRACT C LASS
In Display 15.6 we have slightly rewritten the class Employee from Display 14.1 This time we have made Employee an abstract class The following line (highlighted in Display 15.6) is the only thing that is different from our previous definition of Employee (Display 14.1):
virtual void printCheck( ) const = 0;
pure virtual
function
abstract class
Trang 3The word virtual and the = 0 in the member function heading tell the compiler that this is a pure virtual function and that therefore the class Employee is now an abstract class The imple-mentation for the class Employee includes no definition for the class Employee::printCheck , but otherwise the implementation of the class Employee is the same as before (that is, the same
as in Display 14.2).
It makes sense that there is no definition for the member function Employee::printCheck , since you do not know what kind of check to write until you know with what kind of employee you are dealing In our first definition of the class Employee (Displays 14.1 and 14.2) we were forced to give a definition to Employee::printCheck and so gave one that output an error message saying that the function should not be invoked We now have a more elegant solution By making Employee::printCheck a pure virtual function, we have set things up so that the compiler will enforce the ban against invoking Employee::printCheck
Display 15.6 Interface for the Abstract Class Employee (part 1 of 2)
1
2 //This is the header file employee.h
3 //This is the interface for the abstract class Employee.
4 #ifndef EMPLOYEE_H
5 #define EMPLOYEE_H
6 #include <string>
7 using std::string;
8 namespace SavitchEmployees
9 {
10 class Employee
11 {
12 public :
13 Employee( );
14 Employee(string theName, string theSsn);
15 string getName( ) const ;
16 string getSsn( ) const ;
17 double getNetPay( ) const ;
18 void setName(string newName);
19 void setSsn(string newSsn);
20 void setNetPay( double newNetPay);
21 virtual void printCheck( ) const = 0;
22 private :
23 string name;
24 string ssn;
This is an improved version of the class Employee given in Display 14.1.
The implementation for this class is the same as in Display 14.2, except that no definition is given for the member function printCheck( ).
A pure virtual function
Trang 4640 Polymorphism and Virtual Functions
Self-Test Exercises
3 Is it legal to have an abstract class in which all member functions are pure virtual functions?
4 Given the definition of the class Employee in Display 15.6, which of the following are legal?
a
Employee joe;
joe = Employee( );
b
class HourlyEmployee : public Employee {
public : HourlyEmployee( );
<Some more legal member function definitions, none of which are pure virtual functions.>
private : double wageRate;
double hours;
};
int main( ) {
Employee joe;
joe = HourlyEmployee( );
c
bool isBossOf( const Employee& e1, const Employee& e2);
Display 15.6 Interface for the Abstract Class Employee (part 2 of 2)
25 double netPay;
26 };
27 }//SavitchEmployees
28 #endif //EMPLOYEE_H
Trang 5Pointers and Virtual Functions
Beware lest you lose the substance by grasping at the shadow.
Aesop, The Dog and the Shadow
This section explores some of the more subtle points about virtual functions To understand this material, you need to have covered the material on pointers given in Chapter 10
If Derived is a derived class of the base class Base, then you can assign an object of type
Derived to a variable (or parameter) of type Base, but not the other way around If you consider a concrete example, this becomes sensible For example, DiscountSale is a derived class of Sale (Displays 15.1 and 15.3) You can assign an object of the class
DiscountSale to a variable of type Sale, since a DiscountSale is a Sale However, you cannot do the reverse assignment, since a Sale is not necessarily a DiscountSale The fact that you can assign an object of a derived class to a variable (or parameter) of its base class is critically important for reuse of code via inheritance However, it does have its problems
For example, suppose a program or unit contains the following class definitions: class Pet
{
public :
string name;
virtual void print( ) const ;
};
class Dog : public Pet
{
public :
string breed;
virtual void print( ) const ; //keyword virtual not needed,
//but put here for clarity.
};
Dog vdog;
Pet vpet;
Now concentrate on the data members, name and breed (To keep this example simple,
we have made the member variables public In a real application, they should be private and have functions to manipulate them.)
15.2
Trang 6642 Polymorphism and Virtual Functions
Anything that is a Dog is also a Pet It would seem to make sense to allow programs
to consider values of type Dog to also be values of type Pet, and hence the following should be allowed:
vdog.name = "Tiny";
vdog.breed = "Great Dane";
vpet = vdog;
C++ does allow this sort of assignment You may assign a value, such as the value of
vdog, to a variable of a parent type, such as vpet, but you are not allowed to perform the reverse assignment Although the above assignment is allowed, the value that is assigned to the variable vpet loses its breed field This is called the slicing problem.
The following attempted access will produce an error message:
cout << vpet.breed;
// Illegal: class Pet has no member named breed
You can argue that this makes sense, since once a Dog is moved to a variable of type Pet
it should be treated like any other Pet and not have properties peculiar to Dogs This makes for a lively philosophical debate, but it usually just makes for a nuisance when programming The dog named Tiny is still a Great Dane and we would like to refer to its breed, even if we treated it as a Pet someplace along the way
Fortunately, C++ does offer us a way to treat a Dog as a Pet without throwing away the name of the breed To do this, we use pointers to dynamic variables
Suppose we add the following declarations:
Pet *ppet;
Dog *pdog;
If we use pointers and dynamic variables, we can treat Tiny as a Pet without losing his breed The following is allowed.1
pdog = new Dog;
pdog->name = "Tiny";
pdog->breed = "Great Dane";
ppet = pdog;
Moreover, we can still access the breed field of the node pointed to by ppet Suppose that
Dog::print( ) const ;
has been defined as follows:
void Dog::print( ) const
{
1If you are not familiar with the -> operator, see the subsection of Chapter 10 entitled “The -> Operator.”
slicing
problem
Trang 7cout << "name: " << name << endl;
cout << "breed: " << breed << endl;
}
The statement
ppet->print( );
will cause the following to be printed on the screen:
name: Tiny
breed: Great Dane
This nice ouput happens by virtue of the fact that print( ) is a virtual member func-tion (No pun intended.) We have included test code in Display 15.7
Display 15.7 Defeating the Slicing Problem (part 1 of 2)
1 //Program to illustrate use of a virtual function to defeat the slicing
2 //problem.
3 #include <string>
4 #include <iostream>
5 using std::string;
6 using std::cout;
7 using std::endl;
8 class Pet
9 {
10 public :
11 string name;
12 virtual void print( ) const ;
13 };
14 class Dog : public Pet
15 {
16 public :
17 string breed;
18 virtual void print( ) const ;
19 };
20 int main( )
21 {
22 Dog vdog;
23 Pet vpet;
24 vdog.name = "Tiny";
25 vdog.breed = "Great Dane";
26 vpet = vdog;
27 cout << "The slicing problem:\n";
We have made the member variables public to keep the example simple In a real application they should be private and accessed via member functions.
Keyword virtual is not needed here, but we put it here for clarity.
Trang 8644 Polymorphism and Virtual Functions
Display 15.7 Defeating the Slicing Problem (part 2 of 2)
28 //vpet.breed; is illegal since class Pet has no member named breed.
29 vpet.print( );
30 cout << "Note that it was print from Pet that was invoked.\n";
31 cout << "The slicing problem defeated:\n";
32 Pet *ppet;
33 ppet = new Pet;
34 Dog *pdog;
35 pdog = new Dog;
36 pdog->name = "Tiny";
37 pdog->breed = "Great Dane";
38 ppet = pdog;
39 ppet->print( );
40 pdog->print( );
41 //The following, which accesses member variables directly
42 //rather than via virtual functions, would produce an error:
43 //cout << "name: " << ppet->name << " breed: "
44 // << ppet->breed << endl;
45 //It generates an error message saying
46 //class Pet has no member named breed.
47 return 0;
48 }
49 void Dog::print( ) const
50 {
51 cout << "name: " << name << endl;
52 cout << "breed: " << breed << endl;
53 }
54 void Pet::print( ) const
55 {
56 cout << "name: " << name << endl;
57 }
S AMPLE D IALOGUE
The slicing problem:
name: Tiny
Note that it was print from Pet that was invoked.
The slicing problem defeated:
name: Tiny
breed: Great Dane
name: Tiny
breed: Great Dane
These two print the same output: name: Tiny
breed: Great Dane
Note that no breed is mentioned
Trang 9Object-oriented programming with dynamic variables is a very different way of viewing programming This can all be bewildering at first It will help if you keep two simple rules in mind:
1 If the domain type of the pointer pAncestor is an ancestor class for the domain type
of the pointer pDescendant, then the following assignment of pointers is allowed:
pAncestor = pDescendant;
Moreover, none of the data members or member functions of the dynamic variable being pointed to by pDescendant will be lost
2 Although all the extra fields of the dynamic variable are there, you will need virtual member functions to access them
T HE S LICING P ROBLEM
Although it is legal to assign a derived class object to a base class variable, assigning a derived class object to a base class object slices off data Any data members in the derived class object that are not also in the base class will be lost in the assignment, and any member functions that are not defined in the base class are similarly unavailable to the resulting base class object For example, if Dog is a derived class of Pet , then the following is legal:
Dog vdog;
Pet vpet;
vpet = vdog;
However, vpet cannot be a calling object for a member function from Dog unless the function is also a member function of Pet , and all the member variables of vdog that are not inherited from the class Pet are lost This is the slicing problem.
Note that simply making a member function virtual does not defeat the slicing problem Note the following code from Display 15.7:
Dog vdog;
Pet vpet;
vdog.name = "Tiny";
vdog.breed = "Great Dane";
vpet = vdog;
vpet.print( );
Although the object in vdog is of type Dog , when vdog is assigned to the variable vpet (of type Pet ) it becomes an object of type Pet So, vpet.print( ) invokes the version of print( ) defined in Pet , not the version defined in Dog This happens despite the fact that print( ) is virtual In order to defeat the slicing problem, the function must be virtual and you must use pointers and dynamic variables.
Trang 10646 Polymorphism and Virtual Functions
Tip
Self-Test Exercises
5 Why can’t you assign a base class object to a derived class variable?
6 What is the problem with the (legal) assignment of a derived class object to a base class variable?
7 Suppose the base class and the derived class each has a member function with the same sig-nature When you have a base class pointer to a derived class object and call a function member through the pointer, discuss what determines which function is actually called, the base class member function or the derived class member function
M AKE D ESTRUCTORS V IRTUAL
It is a good policy to always make destructors virtual, but before we explain why this is a good policy we need to say a word or two about how destructors and pointers interact and about what
it means for a destructor to be virtual
Consider the following code, where SomeClass is a class with a destructor that is not virtual: SomeClass *p = new SomeClass;
.
delete p;
When delete is invoked with p , the destructor of the class SomeClass is automatically invoked Now, let’s see what happens when a destructor is marked virtual
The easiest way to describe how destructors interact with the virtual function mechanism is that destructors are treated as if all destructors had the same name (even though they do not really have the same name) For example, suppose Derived is a derived class of the class Base and suppose the destructor in the class Base is marked virtual Now consider the following code: Base *pBase = new Derived;
.
delete pBase;
When delete is invoked with pBase , a destructor is called Since the destructor in the class Base was marked virtual and the object pointed to is of type Derived , the destructor for the class Derived is called (and it in turn calls the destructor for the class Base ) If the destructor in the class Base had not been declared as virtual, then only the destructor in the class Base would be called.
Another point to keep in mind is that when a destructor is marked virtual , then all destructors
of derived classes are automatically virtual (whether or not they are marked virtual ) Again, this behavior is as if all destructors had the same name (even though they do not).
Now we are ready to explain why all destructors should be virtual Consider what happens when destructors are not declared as virtual in a base class In particular consider the base class PFArrayD