On the other hand, if unlike Display 15.8 the destructor for the base class PFArrayD were marked virtual , then when delete is applied to p , the constructor for the class PFArrayDBak w
Trang 1(partially filled array of double s) and its derived class PFArrayDBak (partially filled array of
double s with backup) We discussed these classes in Chapter 14 That was before we knew about
virtual functions, and so the destructor in the base class PFArrayD was not marked virtual In
Display 15.8 we have summarized all the facts we need about the classes PFArrayD and
PFArrayDBak so that you need not look back to Chapter 14.
Consider the following code:
PFArrayD *p = new PFArrayDBak;
.
delete p;
Since the destructor in the base class is not marked virtual , only the destructor for the base
class ( PFArrayD ) will be invoked This will return the memory for the member array a (declared
in PFArrayD ) to the freestore, but the memory for the member array b (declared in
PFArrayD-Bak ) will never be returned to the freestore (until the program ends).
On the other hand, if (unlike Display 15.8) the destructor for the base class PFArrayD were
marked virtual , then when delete is applied to p , the constructor for the class PFArrayDBak
would be invoked (since the object pointed to is of type PFArrayDBak ) The destructor for the
class PFArrayDBak would delete the array b and then automatically invoke the constructor for
the base class PFArrayD , and that would delete the member array a So, with the base class
destructor marked as virtual, all the memory is returned to the freestore To prepare for
eventual-ities such as these, it is best to always mark destructors as virtual.
■ DOWNCASTING AND UPCASTING
You might think some sort of type casting would allow you to easily get around the
slic-ing problem However, thslic-ings are not that simple The followslic-ing is illegal:
Pet vpet;
Dog vdog; //Dog is a derived class with base class Pet.
.
vdog = static_cast <Dog>(vpet); //ILLEGAL!
However, casting in the other direction is perfectly legal and does not even need a
cast-ing operator:
vpet = vdog; //Legal (but does produce the slicing problem.)
Casting from a descendant type to an ancestor type is known as upcasting, since
you are moving up the class hierarchy Upcasting is safe because you are simply
disre-garding some information (disredisre-garding member variables and functions) So, the
fol-lowing is perfectly safe:
vpet = vdog;
upcasting
Trang 2648 Polymorphism and Virtual Functions
Display 15.8 Review of the Classes PFArrayD and PFArrayDBak
class PFArrayD
{
public :
PFArrayD( );
.
~PFArrayD( );
protected :
double *a; //for an array of doubles.
int capacity; //for the size of the array.
int used; //for the number of array positions currently in use.
};
PFArrayD::PFArrayD( ) : capacity(50), used(0)
{
a = new double [capacity];
}
PFArrayD::~PFArrayD( )
{
delete [] a;
}
class PFArrayDBak : public PFArrayD
{
public :
PFArrayDBak( );
~PFArrayDBak( );
private :
double *b; //for a backup of main array.
int usedB; //backup for inherited member variable used.
};
PFArrayDBak::PFArrayDBak( ) : PFArrayD( ), usedB(0)
{
b = new double [capacity];
}
PFArrayDBak::~PFArrayDBak( )
{
delete [] b;
}
The destructors should be virtual, but
we had not yet covered virtual functions when we wrote these classes.
Some details about the derived class PFArrayDBak.
A complete definition of PFArrayDBak is given in Displays 14.10 and 14.11, but this display has all the details you need for this chapter.
Some details about the base class PFArrayD.
A more complete definition of PFArrayD is given in Displays 14.8 and 14.9, but this display has all the details you need for this chapter.
Trang 3Casting from an ancestor type to a descended type is called downcasting and is very
dangerous, since you are assuming that information is being added (added member
variables and functions) The dynamic_cast that we discussed briefly in Chapter 1 is
used for downcasting It is of some possible use in defeating the slicing problem but is
dangerously unreliable and fraught with pitfalls A dynamic_cast may allow you to
downcast, but it only works for pointer types, as in the following:
Pet *ppet;
ppet = new Dog;
Dog *pdog = dynamic_cast <Dog*>(ppet); //Dangerous!
We have had downcasting fail even in situations as simple as this, and so we do not
rec-ommend it
The dynamic_cast is supposed to inform you if it fails If the cast fails, the
dynamic_cast should return NULL (which is really the integer 0).2
If you want to try downcasting keep the following points in mind:
1 You need to keep track of things so that you know the information to be added is
indeed present
2 Your member functions must be virtual, since dynamic_cast uses the virtual
func-tion informafunc-tion to perform the cast
■ HOW C++ IMPLEMENTS VIRTUAL FUNCTIONS
You need not know how a compiler works in order to use it That is the principle of
information hiding, which is basic to all good program design philosophies In
particu-lar, you need not know how virtual functions are implemented in order to use virtual
functions However, many people find that a concrete model of the implementation
helps their understanding, and when reading about virtual functions in other books
you are likely to encounter references to the implementation of virtual functions So,
we will give a brief outline of how they are implemented All compilers for all languages
(including C++) that have virtual functions typically implement them in basically the
same way
If a class has one or more member functions that are virtual, the compiler creates
what is called a virtual function table for that class This table has a pointer (memory
address) for each virtual member function The pointer points to the location of the
correct code for that member function If one virtual function was inherited and not
changed, then its table entry points to the definition for that function that was given in
the parent class (or other ancestor class if need be) If another virtual function had a
new definition in the class, then the pointer in the table for that member function
points to that definition (Remember that the property of being a virtual function is
2The standard says “The value of a failed cast to pointer type is the null pointer of the required
result type A failed cast to a reference type throws a bad_cast.”
downcasting
virtual function table
Trang 4650 Polymorphism and Virtual Functions
Self-Test Exercises
inherited, so once a class has a virtual function table, then all its descendant classes have
a virtual function table.) Whenever an object of a class with one or more virtual functions is created, another pointer is added to the description of the object that is stored in memory This pointer points to the class’s virtual function table When you make a call to a member function using a pointer (yep, another one) to the object, the runtime system uses the virtual function table to decide which definition of a member function to use; it does not use the type of the pointer
Of course, this all happens automatically, so you need not worry about it A com-piler writer is even free to implement virtual functions in some other way as long as it works correctly (although it never actually is implemented in a different way)
8 Why is the following illegal?
Pet vpet;
Dog vdog; //Dog is a derived class with base class Pet.
vdog = static_cast <Dog>(vpet); //ILLEGAL!
■ Late binding means that the decision of which version of a member function is appropriate is decided at runtime In C++, member functions that use late binding
are called virtual functions Polymorphism is another word for late binding.
■ A pure virtual function is a member function that has no definition A pure virtual function is indicated by the word virtual and the notation = 0 in the member function declaration A class with one or more pure virtual functions is called an
abstract class.
■ An abstract class is a type and can be used as a base class to derive other classes However, you cannot create an object of an abstract class type (unless it is an object
of some derived class)
■ You can assign an object of a derived class to a variable of its base class (or any ances-tor class), but the member variables that are not in the base class are lost This is
known as the slicing problem.
■ If the domain type of the pointer pAncestor is a base class for the domain type of the pointer pDescendant, then the following assignment of pointers is allowed:
pAncestor = pDescendant;
Chapter Summary
Trang 5Moreover, none of the data members or member functions of the dynamic variable being pointed to by pDescendant will be lost Although all the extra fields of the dynamic variable are there, you will need virtual member functions to access them
■ It is a good programming practice to make destructors virtual
ANSWERS TO SELF-TEST EXERCISES
1 In essence there is no difference among the three terms They all refer to the same topic There
is only a slight difference in their usage (Virtual function is a kind of member function, late
binding refers to the mechanism used to decide which function definition to use when a
func-tion is virtual, and polymorphism is another name for late binding.)
2 The output would change to the following:
Discounted item is not cheaper.
3 Yes, it is legal to have an abstract class in which all member functions are pure virtual func-tions
4.a Illegal, because Employee is an abstract class
b Legal
c Legal, because an abstract class is a type
5 There would be no members to assign to the derived class’s added members
6 Although it is legal to assign a derived class object to a base class variable, this discards the parts of the derived class object that are not members of the base class This situation is
known as the slicing problem.
7 If the base class function carries the virtual modifier, then the derived class member function is called If the base class member function does not have the virtual modifier, then the base class member function is called
8 Since Dog can have more member variables than Pet, the object vpet may not have
enough data for all the member variables of type Dog
PROGRAMMING PROJECTS
1 Consider a graphics system that has classes for various figures, say rectangles, squares, trian-gles, circles, and so on For example, a rectangle might have data members height, width, and center point, while a square and circle might have only a center point and an edge length or radius, respectively In a well-designed system these would be derived from a common class, Figure You are to implement such a system
The class Figure is the base class You should add only Rectangle and Triangle classes derived from Figure Each class has stubs for member functions erase and draw Each of these member functions outputs a message telling what function has been called and what the class of the calling object is Since these are just stubs, they do nothing more than
Trang 6652 Polymorphism and Virtual Functions
output this message The member function center calls erase and draw to erase and redraw the figure at the center Because you have only stubs for erase and draw, center will not do any “centering” but will call the member functions erase and draw Also, add
an output message in the member function center that announces that center is being called The member functions should take no arguments There are three parts to this project:
a Do the class definitions using no virtual functions Compile and test
b Make the base class member functions virtual Compile and test
c Explain the difference in results
For a real example, you would have to replace the definition of each of these member func-tions with code to do the actual drawing You will be asked to do this in Programming Project 2
Use the following main function for all testing:
//This program tests Programming Problem 1.
#include <iostream>
#include "figure.h"
#include "rectangle.h"
#include "triangle.h"
using std::cout;
int main( )
{
Triangle tri;
tri.draw( );
cout <<
"\nDerived class Triangle object calling center( ).\n";
tri.center( ); //Calls draw and center
Rectangle rect;
rect.draw( );
cout <<
"\nDerived class Rectangle object calling center().\n";
rect.center( ); //Calls draw and center
return 0;
}
2 Flesh out Programming Problem 1 Give new definitions for the various constructors and member functions Figure::center, Figure::draw, Figure::erase, Triangle::draw, Triangle::erase, Rectangle::draw and Rectangle::erase so that the draw func-tions actually draw figures on the screen by placing the character ’*’ at suitable locations
on the screen For the erase functions, you can simply clear the screen (by outputting blank lines or by doing something more sophisticated) There are a lot of details in this and you will have to decide on some of them on your own
For additional online Programming
Projects, click the CodeMate icons
below
1.7
Trang 716 Templates
16.1 FUNCTION TEMPLATES 654
Syntax for Function Templates 656 Pitfall: Compiler Complications 659 Example: A Generic Sorting Function 661 Tip: How to Define Templates 665 Pitfall: Using a Template with an Inappropriate Type 665
16.2 CLASS TEMPLATES 667
Syntax for Class Templates 667 Example: An Array Template Class 671 The vector and basic_string Templates 677
16.3 TEMPLATES AND INHERITANCE 678
Example: Template Class for a Partially Filled Array with Backup 678
CHAPTER SUMMARY 684 ANSWERS TO SELF-TEST EXERCISES 684 PROGRAMMING PROJECTS 688
16_CH16.fm Page 653 Monday, August 18, 2003 1:04 PM
Trang 816 Templates
All men are mortal.
Aristotle is a man
Therefore, Aristotle is mortal.
All X’s are Y.
Z is an X.
Therefore, Z is Y.
All cats are mischievous.
Garfield is a cat.
Therefore, Garfield is mischievous.
A Short Lesson on Syllogisms
INTRODUCTION
This chapter discusses C++ templates, which allow you to define functions and classes that have parameters for type names This enables you to design func-tions that can be used with arguments of different types and to define classes that are much more general than those you have seen before this chapter Section 16.1 requires only material from Chapters 1 through 5 Section 16.2 uses material from Section 16.1 as well as Chapters 1 through 11 but does not require the material from Chapter 12 through 15 Section 16.3 requires the previous sections as well as Chapter 14 on inheritance and all the chapters needed for Section 16.2 Section 16.3 does mark some member func-tions as virtual Virtual functions are covered in Chapter 15 However, this use of virtual functions is not essential to the material presented It is possible
to read Section 16.3 ignoring (or even omitting) all occurrences of the key-word virtual
Function Templates
Many of our previously discussed C++ function definitions have an underlying algorithm that is much more general than the algorithm we gave in the func-tion definifunc-tion For example, consider the funcfunc-tion swapValues, which we first discussed in Chapter 4 For reference, we now repeat the function definition: void swapValues( int & variable1, int & variable2)
{ int temp;
16.1
16_CH16.fm Page 654 Monday, August 18, 2003 1:04 PM
Trang 9Function Templates 655
temp = variable1;
variable1 = variable2;
variable2 = temp;
}
Notice that the function swapValues applies only to variables of type int Yet the algorithm given in the function body could just as well be used to swap the values in two variables of type char If we want to also use the function swapValues with vari-ables of type char, we can overload the function name swapValues by adding the fol-lowing definition:
void swapValues( char & variable1, char & variable2) {
char temp;
temp = variable1;
variable1 = variable2;
variable2 = temp;
}
But there is something inefficient and unsatisfying about these two definitions of the
swapValues function: They are almost identical The only difference is that one defini-tion uses the type int in three places and the other uses the type char in the same three places Proceeding in this way, if we wanted to have the function swapValues apply to pairs of variables of type double, we would have to write a third almost identical func-tion definifunc-tion If we wanted to apply swapValues to still more types, the number of almost identical function definitions would be even larger This would require a good deal of typing and would clutter up our code with lots of definitions that look identi-cal We should be able to say that the following function definition applies to variables
of any type:
void swapValues( Type_Of_The_Variables & variable1,
Type_Of_The_Variables & variable2) {
Type_Of_The_Variables temp;
temp = variable1;
variable1 = variable2;
variable2 = temp;
}
As we will see, something like this is possible We can define one function that applies
to all types of variables, although the syntax is a bit more complicated than what we have shown above The proper syntax is described in the next subsection
16_CH16.fm Page 655 Monday, August 18, 2003 1:04 PM
Trang 10656 Templates
■ SYNTAX FOR FUNCTION TEMPLATES
Display 16.1 shows a C++ template for the function swapValues This function tem-plate allows you to swap the values of any two variables, of any type, as long as the two variables have the same type The definition and the function declaration begin with the line
template < class T>
This is often called the template prefix, and it tells the compiler that the definition or function declaration that follows is a template and that T is a type parameter In this context the word class actually means type.1 As we will see, the type parameter T can
be replaced by any type, whether the type is a class or not Within the body of the func-tion definifunc-tion the type parameter T is used just like any other type
The function template definition is, in effect, a large collection of function defini-tions For the function template for swapValues shown in Display 16.1, there is, in effect, one function definition for each possible type name Each of these definitions is obtained by replacing the type parameter T with a type name For example, the func-tion definifunc-tion shown below is obtained by replacing T with the type name double: void swapValues( double & variable1, double & variable2)
{ double temp;
temp = variable1;
variable1 = variable2;
variable2 = temp;
}
Another definition for swapValues is obtained by replacing the type parameter T in the function template with the type name int Yet another definition is obtained by replacing the type parameter T with char The one function template shown in Display 16.1 overloads the function name swapValues so that there is a slightly different func-tion definifunc-tion for every possible type
The compiler will not literally produce definitions for every possible type for the function name swapValues, but it will behave exactly as if it had produced all those function definitions A separate definition will be produced for each different type for which you use the template, but not for any types you do not use Only one definition
is generated for a single type regardless of the number of times you use the template for that type Notice that the function swapValues is called twice in Display 16.1: One
1In fact, the ANSI/ISO standard provides that the keyword typename may be used instead of class in the template prefix It would make more sense to use the keyword typename rather than class, but everybody uses class, so we will do the same (It is often true that consistency
in coding is more important than optimality.)
template
prefix
type
parameter
A template
overloads
the function
name
A template
overloads
the function
name
16_CH16.fm Page 656 Monday, August 18, 2003 1:04 PM