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

Absolute C++ (4th Edition) part 64 pdf

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 (174.71 KB, 10 trang )

Virtual Function Basics 637
Pitfall
Self-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.

ABSTRACT CLASSES AND PURE VIRTUAL FUNCTIONS
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
638 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
Virtual Function Basics 639
The 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
640 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
Pointers and Virtual Functions 641
Pointers 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.

VIRTUAL FUNCTIONS AND EXTENDED TYPE COMPATIBILITY
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
642 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
{
1
If you are not familiar with the -> operator, see the subsection of Chapter 10 entitled “The ->
Operator.”
slicing
problem

Pointers and Virtual Functions 643
cout << "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.
644 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
Pointers and Virtual Functions 645
Pitfall
Object-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.
646 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

×