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

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

10 361 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 174,71 KB

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

Nội dung

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 1

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.

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 2

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

Trang 3

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

Trang 4

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

Trang 5

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

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 6

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

{

1If you are not familiar with the -> operator, see the subsection of Chapter 10 entitled “The -> Operator.”

slicing

problem

Trang 7

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.

Trang 8

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

Trang 9

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.

Trang 10

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

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

TỪ KHÓA LIÊN QUAN