1. Trang chủ
  2. » Công Nghệ Thông Tin

Absolute C++ (4th Edition) part 65 doc

10 263 0
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 10
Dung lượng 287,29 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

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 2

648 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 3

Casting 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 4

650 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 5

Moreover, 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 6

652 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 7

16 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 8

16 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 9

Function 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 10

656 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

Ngày đăng: 04/07/2014, 05:21

TỪ KHÓA LIÊN QUAN