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

10 360 0
Absolute C++ (4th Edition) part 64 pdf

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

Thông tin tài liệu

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

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

Từ khóa liên quan

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan