15 Polymorphism and Virtual Functions15.1 VIRTUAL FUNCTION BASICS 628 Late Binding 628 Virtual Functions in C++ 629 Tip: The Virtual Property Is Inherited 636 Tip: When to Use a Virtual
Trang 115 Polymorphism and Virtual Functions
15.1 VIRTUAL FUNCTION BASICS 628
Late Binding 628 Virtual Functions in C++ 629 Tip: The Virtual Property Is Inherited 636 Tip: When to Use a Virtual Function 636 Pitfall: Omitting the Definition of a Virtual Member Function 637
Abstract Classes and Pure Virtual Functions 637 Example: An Abstract Class 638
15.2 POINTERS AND VIRTUAL FUNCTIONS 641
Virtual Functions and Extended Type Compatibility 641 Pitfall: The Slicing Problem 645
Tip: Make Destructors Virtual 646 Downcasting and Upcasting 647 How C++ Implements Virtual Functions 649
CHAPTER SUMMARY 650 ANSWERS TO SELF-TEST EXERCISES 651 PROGRAMMING PROJECTS 651
Trang 215 Polymorphism and Virtual Functions
I did it my way.
Frank Sinatra
INTRODUCTION
Polymorphism refers to the ability to associate many meanings to one function name by means of a special mechanism known as virtual functions or late bind-ing Polymorphism is one of the fundamental mechanisms of a popular and powerful programming philosophy known as object-oriented programming Wow, lots of fancy words! This chapter will explain them
Section 15.1 does not require the material from Chapters 10 (pointers and dynamic arrays), 12 (file I/O), or 13 (recursion) Section 15.2 does not require the material from Chapters 12 (file I/O) or 13 (recursion), but does require the material from Chapter 10 (pointers and dynamic arrays)
Virtual Function Basics
virtual adj.1 Existing or resulting in essence or effect though not in actual fact, form, or name
The American Heritage Dictionary of the English Language, Third Edition
A virtual function is so named because it may, in a sense to be made clear, be used before it is defined Virtual functions will prove to be another tool for software reuse
Virtual functions are best explained by an example 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 For example, the Rectangle class might have member vari-ables for a height, width, and center point, while the Circle class might have member variables for a center point and a radius In a well-designed program-ming 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 differ-ent function to draw its kind of figure However, because the functions belong
15.1
Trang 3Virtual Function Basics 629
to the classes, they can all be called draw 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 All this is not news, but now we move on to something new: virtual functions
defined in the parent class Figure
The parent class Figure may have functions that apply to all figures For example, it
might 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::cen-ter might use the function draw to redraw the figure in the center of the screen When
you think of using the inherited function center with figures of the classes Rectangle
and Circle, you begin to see that there are complications here
To make the point clear and more dramatic, let’s suppose that the class Figure is
already written and in use and that at some later time you add a class for a brand-new
kind of figure, say, the class Triangle Now, Triangle can be a derived class of the class
Figure, and so the function center will be inherited from the class Figure The
func-tion center should therefore apply to (and perform correctly for) all Triangles But
there is a complication The function center uses draw, and the function draw is
differ-ent for each type of figure The inherited function center (if nothing special is done)
will use the definition of the function draw given in the class Figure, and that function
draw does not work correctly for Triangles We want the inherited member function
center to use the function Triangle::draw rather than the function Figure::draw
But the class Triangle—and therefore the function Triangle::draw—was not even
written when the function center (defined in the class Figure) was written and
com-piled! How can the function center possibly work correctly for Triangles? The
com-piler did not know anything about Triangle::draw at the time that center was
compiled! The answer is that it can apply provided draw is a virtual function
When you make a function virtual, you are telling the compiler “I do not know
how this function is implemented Wait until it is used in a program, and then get the
implementation from the object instance.” The technique of waiting until runtime to
determine the implementation of a function is often called late binding or dynamic
binding Virtual functions are the way C++ provides late binding But enough
intro-duction We need an example to make this come alive (and to teach you how to use
vir-tual functions in your programs) To explain the details of virvir-tual functions in C++, we
will use a simplified example from an application area other than drawing figures
Suppose you are designing a record-keeping program for an automobile parts store You
want to make the program versatile, but you are not sure you can account for all
possi-ble situations For example, you want to keep track of sales, but you cannot anticipate
all types of sales At first, there will only be regular sales to retail customers who go to
the store to buy one particular part However, later you may want to add sales with
dis-counts or mail order sales with a shipping charge All these sales will be for an item with
a basic price and ultimately will produce some bill For a simple sale, the bill is just the
basic price, but if you later add discounts, then some kinds of bills will also depend on
virtual function
late binding or
dynamic binding
Trang 4630 Polymorphism and Virtual Functions
the size of the discount Your program will need to compute daily gross sales, which
intuitively should just be the sum of all the individual sales bills You may also want to
calculate the largest and smallest sales of the day or the average sale for the day All these
can be calculated from the individual bills, but many of the functions for computing
the bills will not be added until later, when you decide what types of sales you will be
dealing with To accommodate this, we make the function for computing the bill a
vir-tual function (For simplicity in this first example, we assume that each sale is for just
one item, although with derived classes and virtual functions we could, but will not
here, account for sales of multiple items.)
Displays 15.1 and 15.2 contain the interface and implementation for the class Sale
All types of sales will be derived classes of the class Sale The class Sale corresponds to
Display 15.1 Interface for the Base Class Sale
1
2 //This is the header file sale.h
3 //This is the interface for the class Sale.
4//Sale is a class for simple sales.
5 #ifndef SALE_H
6 #define SALE_H
7 namespace SavitchSale
8 {
9 class Sale
10 {
11 public :
12 Sale( );
13 Sale( double thePrice);
14 double getPrice( ) const ;
15 void setPrice( double newPrice);
16 virtual double bill( ) const ;
17 double savings( const Sale& other) const ;
18 //Returns the savings if you buy other instead of the calling object.
19 private :
20 double price;
21 };
22 bool operator < ( const Sale& first, const Sale& second);
23 //Compares two sales to see which is larger.
24 } //SavitchSale
25 #endif // SALE_H
Trang 5Virtual Function Basics 631
Display 15.2 Implementation of the Base Class Sale (part 1 of 2)
1
2 //This is the file sale.cpp.
3 //This is the implementation for the class Sale.
4//The interface for the class Sale is in the file sale.h.
5 #include <iostream>
6 #include "sale.h"
7 using std::cout;
8 namespace SavitchSale
9 {
10 Sale::Sale( ) : price(0)
11 {
12 //Intentionally empty
13 }
14 Sale::Sale( double thePrice)
15 {
16 if (thePrice >= 0)
17 price = thePrice;
18 else
19 {
20 cout << "Error: Cannot have a negative price!\n";
21 exit(1);
22 }
23 }
24 double Sale::bill( ) const
25 {
26 return price;
27 }
28 double Sale::getPrice( ) const
29 {
30 return price;
31 }
32
33 void Sale::setPrice( double newPrice)
34 {
35 if (newPrice >= 0)
36 price = newPrice;
37 else
38 {
39 cout << "Error: Cannot have a negative price!\n";
40 exit(1);
Trang 6632 Polymorphism and Virtual Functions
simple sales of a single item with no added discounts or charges Notice the reserved word virtual in the declaration for the member function bill (Display 15.1) Notice (Display 15.2) that the member function savings and the overloaded operator, <, each use the function bill Since bill is declared to be a virtual function, we can later define derived classes of the class Sale and define their versions of the member function
bill, and the definitions of the member function savings and the overloaded operator,
<, which we gave with the class Sale, will use the version of the member function bill
that corresponds to the object of the derived class
For example, Displays 15.3 and 15.4 show the derived class DiscountSale Notice that the class DiscountSale requires a different definition for its version of the member function bill Nonetheless, when the member function savings and the overloaded operator, <, are used with an object of the class DiscountSale, they will use the version
of the function definition for bill that was given with the class DiscountSale This is indeed a pretty fancy trick for C++ to pull off Consider the function call d1.sav-ings(d2) for objects d1 and d2 of the class DiscountSale The definition of the function
savings (even for an object of the class DiscountSale) is given in the implementation file for the base class Sale, which was compiled before we even thought of the class
DiscountSale Yet, in the function call d1.savings(d2), the line that calls the function
bill knows enough to use the definition of the function bill given for the class
DiscountSale
How does this work? In order to write C++ programs you can just assume it happens
by magic, but the real explanation was given in the introduction to this section When you label a function virtual, you are telling the C++ environment “Wait until this function is used in a program, and then get the implementation corresponding to the calling object.”
Display 15.5 gives a sample program that illustrates how the virtual function bill
and the functions that use bill work in a complete program
Display 15.2 Implementation of the Base Class Sale (part 2 of 2)
41 }
42 }
43 double Sale::savings( const Sale& other) const
44 {
45 return (bill( ) - other.bill( ));
46 }
47 bool operator < ( const Sale& first, const Sale& second)
48 {
49 return (first.bill( ) < second.bill( ));
50 }
51 } //SavitchSale
Trang 7Virtual Function Basics 633
Display 15.3 Interface for the Derived Class DiscountSale
1
2 //This is the file discountsale.h.
3 //This is the interface for the class DiscountSale.
4 #ifndef DISCOUNTSALE_H
5 #define DISCOUNTSALE_H
6 #include "sale.h"
7 namespace SavitchSale
8 {
9 class DiscountSale : public Sale
10 {
11 public :
12 DiscountSale( );
13 DiscountSale( double thePrice, double theDiscount);
14 //Discount is expressed as a percentage of the price.
15 //A negative discount is a price increase.
16 double getDiscount( ) const ;
17 void setDiscount( double newDiscount);
18 double bill( ) const ;
19 private :
20 double discount;
21 };
22 } //SavitchSale
23 #endif //DISCOUNTSALE_H
Since bill was declared virtual in the base class,
it is automatically virtual in the derived class DiscountSale You can add the modifier virtual to the declaration of bill or omit it as here; in either case bill is virtual in the class DiscountSale (We prefer
to include the word virtual in all virtual function declarations, even if it is not required We omitted it here to illustrate that it is not required.)
Display 15.4 Implementation for the Derived Class DiscountSale (part 1 of 2)
1
2 //This is the implementation for the class DiscountSale.
3 //This is the file discountsale.cpp.
4//The interface for the class DiscountSale is in the header file
5 //discountsale.h.
6 #include "discountsale.h"
7 namespace SavitchSale
8 {
9 DiscountSale::DiscountSale( ) : Sale( ), discount(0)
10 {
Trang 8634 Polymorphism and Virtual Functions
Display 15.4 Implementation for the Derived Class DiscountSale (part 2 of 2)
11 //Intentionally empty
12 }
13 DiscountSale::DiscountSale( double thePrice, double theDiscount)
14 : Sale(thePrice), discount(theDiscount)
15 {
16 //Intentionally empty
17 }
18 double DiscountSale::getDiscount( ) const
19 {
20 return discount;
21 }
22 void DiscountSale::setDiscount( double newDiscount)
23 {
24 discount = newDiscount;
25 }
26 double DiscountSale::bill( ) const
27 {
28 double fraction = discount/100;
29 return (1 - fraction)*getPrice( );
30 }
31 } //SavitchSale
You do not repeat the qualifier virtual in the function definition.
Display 15.5 Use of a Virtual Function (part 1 of 2)
1
2 //Demonstrates the performance of the virtual function bill.
3 #include <iostream>
4 #include "sale.h" //Not really needed, but safe due to ifndef.
5 #include "discountsale.h"
6 using std::cout;
7 using std::endl;
8 using std::ios;
9 using namespace SavitchSale;
10 int main( )
11 {
Trang 9Virtual Function Basics 635
V IRTUAL F UNCTION
A virtual function is indicated by including the modifier virtual in the member function decla-ration (which is given in the definition of the class)
If a function is virtual and a new definition of the function is given in a derived class, then for any object of the derived class, that object will always use the definition of the virtual function that was given in the derived class, even if the virtual function is used indirectly by being invoked in the definition of an inherited function This method of deciding which definition of a virtual func-tion to use is known as late binding.
P OLYMORPHISM
PPP
Pooo ollllyyy ym m mooo orrrrp p phhh hiiiissssm m refers to the ability to associate many meanings to one function name by means of m the late-binding mechanism Thus, polymorphism, late binding, and virtual functions are really all the same topic.
Display 15.5 Use of a Virtual Function (part 2 of 2)
12 Sale simple(10.00); //One item at $10.00.
13 DiscountSale discount(11.00, 10); //One item at $11.00 with a 10% discount.
14 cout.setf(ios::fixed);
15 cout.setf(ios::showpoint);
16 cout.precision(2);
17 if (discount < simple)
18 {
19 cout << "Discounted item is cheaper.\n";
20 cout << "Savings is $" << simple.savings(discount) << endl;
21 }
22 else
23 cout << "Discounted item is not cheaper.\n";
24 return 0;
25 }
S AMPLE D IALOGUE
Discounted item is cheaper.
Savings is $0.10
The objects discount and simple use different code for the member function bill when the less-than comparison is made Similar remarks apply to savings.
Trang 10636 Polymorphism and Virtual Functions
Tip
Tip T HE V IRTUAL P ROPERTY I S I NHERITED
The property of being a virtual function is inherited For example, since bill was declared to be virtual in the base class Sale (Display 15.1), the function bill is automatically virtual in the derived class DiscountSale (Display 15.3) So, the following two declarations of the member function bill would be equivalent in the definition of the derived class DiscountSale :
double bill( ) const ;
virtual double bill( ) const ;
Thus, if SuperDiscountSale is a derived class of the class DiscountSale that inherits the function savings , and if the function bill is given a new definition for the class SuperDis-countSale , then all objects of the class SuperDiscountSale will use the definition of the function bill given in the definition of the class SuperDiscountSale Even the inherited func-tion savings (which includes a call to the function bill ) will use the definition of bill given in
SuperDiscountSale whenever the calling object is in the class SuperDiscountSale
W HEN TO U SE A V IRTUAL F UNCTION
There are clear advantages to using virtual functions and no clear disadvantages that we have seen so far So, why not make all member functions virtual? In fact, why not define the C++ com-piler so that (like some other languages, such as Java) all member functions are automatically vir-tual? The answer is that there is a large overhead to making a function virtual Doing so uses more storage and makes your program run slower than if the function were not virtual That is why the designers of C++ gave the programmer control over which member functions are virtual and which are not If you expect to need the advantages of a virtual member function, then make that mem-ber function virtual If you do not expect to need the advantages of a virtual function, then your program will run more efficiently if you do not make the member function virtual.
O VERRIDING
When a virtual function definition is changed in a derived class, programmers often say the
function definition is overridden In the C++ literature a distinction is usually made between the
terms redefined and overridden Both terms refer to changing the definition of the function in a derived class If the function is a virtual function, this act is called overriding If the function is not a virtual function, it’s called redefining This may seem like a silly distinction to you the pro-grammer, since you do the same thing in both cases, but the two cases are treated differently by the compiler.