Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 120 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
120
Dung lượng
2,08 MB
Nội dung
file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm Figure 15.12. Static and dynamic binding for base and derived class pointers. Figure 15-12 shows base class pointers (narrow ones) and derived class pointers (fat one, with two parts) that point to base class objects (the dashed part represents the missing derived part) and to derived class objects (the left part represents the base part, the right part represents the derived part). The vertical lines inside each part represent member functions of four types. Type 1 is defined in the base class and is inherited in the derived class as is. Type 2 is added to the derived class without a counterpart in the base class. Type 3 is defined in the base class and redefined in the derived class with the same name (with the same or different signatures) Type 4 is defined in the base class (as virtual) and is redefined in the derived class with the same name and the same signature. The methods that can be called through the pointer are underlined. In case A, functions of types 3 and 4 defined in the base class are hidden by the functions defined in the derived class. In case B, only functions defined in the base class are accessible. In case C, only functions defined in the base class are accessible, but the functions redefined in the derived class as virtual hide their base class counterparts and can be called dynamically. In case D, only functions defined in the base class and not redefined in the derived class can be called. Table 15.1 summarizes the same rules. The columns describe kinds of objects and kinds of pointers that point to these objects, the rows describe different kinds of member functions. file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (961 of 1187) [8/17/2002 2:58:07 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm Table 15.1. Summary of Rules for Static and Dynamic Binding Kind of Member Function Base Pointers Derived Pointers Base object Derived object Base object Derived object Functions defined in the Base class Inherited in Derived class as available available available available Redefined in Derived (non-virtual) available available not available hidden Redefined in Derived (virtual) available hidden not available hidden Functions defined in the Derived class Defined in Derived class only syntax error syntax error crash available Redefined in Derived (non-virtual) not available not available crash available Redefined in Derived (virtual) not available dynamic binding crash available Pure Virtual Functions Base virtual functions may have no job to do because they have no meaning within the application. Their job is just to define the interface as a standard for its derived classes. This is why virtual functions are introduced in the first place. For example, the write() method in class Person does not contain anything. It has no code. Notice too that it is never called. All calls to the write() method in the client code (global function write()) are resolved either to a Faculty class or to a Student class method write(). Actually, class Person is a pure generalization with no real role. There are no Person objects in the application. All objects are created with the operator new in the global function read() and are either of Student or Faculty class. The description of the problem at the beginning of this section simply says that there are two types of records, one for students and one for faculty members. Class Person was first introduced into the application as an abstraction that merges the characteristics of faculty objects and student objects into one generalized class (Listing 15.3). Later, it was used to define a hierarchy of derived classes ( Listing 15.4). In the last version of the program (Listing 15.4), class Person was used to define the interface of the virtual function write(). In real life, class Person might be a very useful class. In addition to the university id and the name, it can contain date of birth, address, phone number, and a host of other characteristics that are common to Faculty and Student objects. In addition to data, class Person can define numerous methods such as change of name, address or phone number, retrieval of the university id, and other data common to Faculty and Student objects. The derived classes can inherit all these useful functions. The clients of derived classes can use these functions by sending these messages (defined in the Person class) to objects of classes Faculty and Student. Again, I am not saying that class Person is useless. I am saying that objects of class Person are useless for this file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (962 of 1187) [8/17/2002 2:58:07 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm application. The application needs only objects of classes derived from Person. Please keep this distinction in mind. The Person class designer knows that the application does not create class objects and that class objects have no job to do. It would be nice to pass this knowledge on to the client programmer (and to maintainers), not in comments, but rather in code. C++ allows you to define the base class in such a way that an attempt to create an object of this type would be illegal and would result in a syntax error. C++ makes it possible through the use of pure virtual functions and abstract classes. I am not sure why two terms, "pure" and "abstract," are used to describe the same idea. A pure virtual function is a virtual function that should not be called (like write() in class Person). If the program tries to call this function, a syntax error results. An abstract class is a class with at least one pure virtual function (more than one is okay). It is illegal to create objects of this class. If the program tries to create an object of this class either dynamically or on the stack, a syntax error results. There are no C++ keywords for pure virtual functions and abstract classes. Instead, a pure virtual function is recognized (by the compiler, client programmer, and maintainer) as a member function whose declaration is "initialized" to zero. Here is the class Person whose write() member function is defined as a pure virtual function. struct Person { // abstract class protected: char id[10]; // data common to both types char* name; public: Person(const char id[], const char nm[]); virtual void write() const = 0; // pure virtual function ~Person(); } ; Of course, the assignment operator does not denote the assignment here. This is just another example of giving a symbol an additional meaning in yet another context. This is confusing. Adding another keyword, such as pure or abstract, would probably be a better design. A pure virtual function has no implementation. Actually, it is a syntax error to supply the implementation of the pure virtual function (or to invoke the function). It is the presence of virtual functions that makes a class an abstract (or partial) class. In addition to the lack of instantiated objects, an abstract class is required to have at least one file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (963 of 1187) [8/17/2002 2:58:07 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm derived class. If the derived class implements this function, it becomes a regular class. If the derived class does not implement this function, it becomes an abstract class as well. It is illegal to create objects of that derived class, and this class must have at least one derived class. The derived classes implement the pure virtual functions in the same way they implement regular virtual functions. This means that the derived class should use the same name, the same signature, and the same return type as the pure virtual function should use. The mode of derivation should be public. Here is an example of class Faculty that implements the virtual function write(). It is a regular non-abstract class. struct Faculty : public Person { // regular class private: char* rank; // for faculty only public: Faculty(const char id[], const char nm[], const char r[]); void write () const; // regular virtual function ~Faculty(); } ; Actually, this is the same derived class I used in Listing 15.5. Looking at the regular non-abstract class, you cannot say whether it is derived from an abstract class or from a regular class. This is perfectly all right, because for the user of the class Faculty it does not matter how the base class Person is implemented, as long as the client code does not try to instantiate the objects of the abstract class. For a regular class with virtual functions, the client code can create objects of this class, send messages to these objects, and use polymorphism if needed. Again, an abstract class is a C++ class in all regards; it can have data members and regular nonpure functions, including virtual functions. If a class inherits a virtual function as a pure virtual function without defining its body, this derived class is also an abstract class: No objects of that class can be created. If the client code needs objects of that class but is not going to call that function on these objects (because this function has no job yet), an empty body of the function can be used. The class becomes a regular, non-abstract class, and an object of that class can be created. class Base { // abstract class public: virtual void member() = 0; // pure virtual function file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (964 of 1187) [8/17/2002 2:58:07 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm ¡K. } ; // the rest of Base class class Derived : public Base { // regular class public: void member() // virtual function { } // empty body: noop ¡K. } ; // the rest of Derived class Here, class Base is an abstract class. The objects of this class cannot be created. Class Derived is a regular class. Its objects can be instantiated both on the stack (as named variables) and on the heap (as unnamed variables). Function member() in the Base class is a pure virtual function. It cannot be called. Function member() in the Derived class is a regular virtual function. However, its call results in no operation. Base *b; Derived *d; // Base and Derived pointers b = new Base; // syntax error, abstract class d = new Derived; // OK, regular class, heap object b = new Derived; // OK, implicit pointer conversion d->member(); // OK, compile time binding, no op b->member(); // OK, run time binding d->Base::member(); // linker error: no implementation Redefinition with a different signature makes the function non-virtual in the derived class. Here, class Derived1 is a class that inherits from the abstract class Base but does not redefine the pure function member() with no parameters. Instead, it defines a function member(int) with one parameter. class Derived1 : public Base { // also an abstract class public: void member(int) // non-virtual function { } // empty body: noop ¡K. } ; // the rest of Derived1 class This means that class Derived1 is an abstract class. It is a syntax error to create objects of this class. Since this class is not used as a base class to derive other classes, it is quite useless, since there is no way one can use any of its functions. class Derived2 : public Derived1 { // regular class file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (965 of 1187) [8/17/2002 2:58:07 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm public: void member() // virtual function { } // empty body: noop ¡K. } ; // the rest of Derived class Class Derived2 inherits from class Derived1. It implements the virtual member function member(), and hence it is legal to create objects of this class. The objects of this class can respond to the message member(), both with run-time binding and with static binding. The objects of this class cannot respond to the message member(int) because this function is hidden by the member function member() defined in class Derived2. Derived2 *d2 = new Derived2; // OK, regular class, heap object d2->member(); // OK, static binding b = new Derived2; // OK for virtual functions b->member(); // OK, dynamic binding b->member(0); // syntax error d2->member(0); // wrong number of parameters Notice that the base class pointer b, when pointing to the derived class object, can invoke only ϒΠ non-pure member (virtual or non-virtual) functions defined in the base class ϒΠ virtual functions defined in the derived class It cannot invoke non-virtual member functions defined in the derived class. It is a myopic pointer. It takes a virtual function to extend its scope of vision to the derived part of the object it is pointing to. Otherwise, it can see only the base part of the derived object. It takes the derived class pointer to access both the base part and the derived part of the derived object. Virtual Functions: Destructors When the delete operator is invoked, the destructor is called and the object is destroyed. Which destructor is called? The destructor defined in the class of the pointer pointing to the object or the destructor defined in the class to which the object pointed to by the pointer belongs? When the pointer and the object pointed to by the pointer belong to the same class, the answer is simple: the destructor is of that class. file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (966 of 1187) [8/17/2002 2:58:07 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm Derived2 *d2 = new Derived2; // OK, regular class, heap object d2->member(); // OK, static binding b = new Derived2; // OK for virtual functions b->member(); // OK, dynamic binding delete d2; // class Derived2 destructor delete b; // ?? C++ destructors are member functions. They are regular non-virtual member functions. When the delete operator is used, the compiler finds the definition of the pointer operand, searches the definition of the class to which the pointer belongs, and calls the destructor that belongs to that class. All this happens at compile time. The compiler pays no attention to the class of object to which the pointer is pointing. When the pointer and the object are of the same class there is no problem: The dynamic memory (and other resources) allocated to the object are returned as the destructor code executes. When the derived class pointer points to a base object¡Xwell, you should not do that. The big and powerful derived class pointer will require the little base object to do things it cannot do. Person p; Faculty f; // base and derived pointers p = new Person("U12345678", "Smith"); f = p; // syntax error: stay away f = (Faculty*)p; // I insist I know what I do delete f; // Faculty destructor In this example, the Faculty destructor is invoked on a Person object. The delete operator is called for the data member rank that is not in the object. The results are undefined. When a base pointer points to a derived class object, the base class destructor is called. This might or might not be troublesome. If the dynamic memory is handled in the base class but not in the derived class, there is no problem; the heap memory is returned by the base destructor. If the derived class handles the heap memory, it is not returned by the base destructor. A memory leak results. Person *p; Faculty* f; // base and derived pointers f = new Faculty("U12345689", "Black", "Assistant Professor"); p = f; // or p = (Person*) f; delete p; // memory leak file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (967 of 1187) [8/17/2002 2:58:07 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm In this example, the delete operator invokes the Person destructor, which deletes dynamic memory allocated for the name. The Faculty destructor is not called, and the heap memory allocated for the rank is not returned. In Listing 15.5, the client code uses a loop to go over the array of base pointers and delete each object allocated dynamically at the beginning of the execution. For each object in the data structure, the Person destructor is executed. for (int j=0; j < cnt; j++) { delete data[j]; } // delete Person heap memory As far as Faculty and Student objects are concerned, their memory is returned completely. The delete operator deletes the object no matter what the type of the object. The problem is not with the object memory but with the heap memory allocated to the derived class objects, the right-most part on Figure 15-10. The Person destructor deletes the heap memory allocated for name but not the heap memory allocated for rank and major. When a derived object is destroyed through a base pointer, only the base destructor is called, and the derived class destructor is not called. C++ offers a way out of this: to declare the base destructor virtual. By convention, it makes every derived class destructor also virtual. When the delete operator is applied to a base pointer, the target class destructor is called polymorphically (and then the base class destructor, if any). struct Person { // abstract class protected: char id[10]; // data common to both types char* name; public: Person(const char id[], const char nm[]); virtual void write() const = 0; // pure virtual function virtual ~Person(); // this makes the trick } ; struct Faculty : public Person { // regular class private: char* rank; // for faculty only public: Faculty(const char id[], const char nm[], const char r[]); void write () const; // regular virtual function ~Faculty(); // now this is virtual, too } ; file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (968 of 1187) [8/17/2002 2:58:07 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm This solution is somewhat awkward. After all, the first thing you should remember about virtual functions is that you use exactly the same name everywhere, in the base class and in all derived classes. With destructors, this is not the case because each destructor has the same name as the class name. Hence, destructors violate the rules of virtual functions. This is similar to constructors, and, indeed, there are no virtual constructors in C++. However, practical considerations are more important that logical beauty. Memory leaks are too dangerous to put up with. This is why C++ allows this inconsistency and supports virtual destructors. Multiple Inheritance: Several Base Classes In C++, a derived class might have more than one base class. With single inheritance, the hierarchy of classes is a tree, with the base class on the top of the hierarchy and derived classes below the base class. With multiple inheritance, the hierarchy of classes might become a graph rather than a tree as with single inheritance. This makes the relationships among classes more complex than with single inheritance. The issues related to multiple inheritance are more difficult to understand than for simple inheritance. Similar to single inheritance, multiple inheritance is not so much a technique to make the client code simpler or easier to understand; it is a technique that makes the server code easier to produce. Unlike single inheritance, multiple inheritance allows the server class designer to mix the characteristics of diverse classes in one class. Let us consider a simple example. Let us assume that class B1 provides the clients with a public service f1() and class B2 provides the clients with a public service f2(). This is almost exactly what the client code needs, but in addition to these two services the client code needs a public service f3() to be available. One possible technique to serve the client is to merge characteristics of classes B1 and B2 into one class using multiple inheritance. class B1 { public: void f1(); // public service f1() ¡K }; // the rest of class B1 class B2 { public: void f2(); // public service f2() file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (969 of 1187) [8/17/2002 2:58:07 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm ¡K }; // the rest of class B2 By inheriting publicly from class B1 and B2, class Derived becomes able to provide its clients with the union of services provided by each of the base classes, in this case, methods f1() and f2(). This means that to provide its clients with all three services (f1(), f2(), f3(),) the designer of class Derived has to implement only one function, f3(). This is quite an accomplishment. class Derived : public B1, public B2 // two base classes { public: // f1(), f2() are inherited void f3(); // f3() is added to services ¡K }; // the rest of class Derived Now the client code can instantiate Derived objects and send to them messages that they inherited from both base classes and messages that are added by the Derived class. Derived d; // instantiate Derived object d.f1(); d.f2(); // inherited services (B1, B2) d.f3(); // the service is added in the Derived class We see that a derived class provides clients with capabilities of all the bases plus its own data and behavior. Initially, C++ did not have multiple inheritance. But Stroustrup, the designer of C++, complained that programmers "demanded multiple inheritance," and now C++ has it. I am not sure to what extent this was a result of the external pressure. I know of quite a few suggestions that he did not give in to. Multiple inheritance is good for customizing existing class libraries, for example, for adding or redefining members in existing classes. Derived classes represent a combination of base classes rather than a refinement of a single base class. Each parent class contributes properties to the derived class; the derived class is a union of the base features. Examples of using multiple inheritance include graphic objects, NOW accounts, and iostream classes in the C++ standard library. For a graphics package, classes Shape and Position were used as base classes to produce class file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (970 of 1187) [8/17/2002 2:58:07 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com [...]... inheritance: The member initialization list should be used In the next example, the base class B1 has one data member, the base class B2 has another data member, and the derived class has yet another data member (a dynamically allocated character array) The Derived class should provide a constructor with three parameters so that it can pass data to its B1 and B2 components and to its own data member class... operator is overloaded as a class member, it can have whatever arguments are appropriate The target of the message will be used as the first operand If an operator is overloaded as a global function, it has to have at least one class argument; it cannot have arguments of built-in types only This limitation does not apply to memory management operators (new, delete, and delete []) file://///Administrator/General%20English%20Learning/it2002-7-6 /core. htm... binary overloaded operators have only one parameter, because another parameter becomes the target object of the message Similarly, unary built-in operators remain unary when they are overloaded If a unary overloaded operator is implemented as a global nonmember unary operator (e.g., a friend), it will have one parameter If this overloaded operator is defined as a member function (which is sent as a. .. as a message to a target object), it will have no parameters As a simple example, consider class Account similar to one discussed in Chapter 13, "Similar Classes: How to Treat Them." The class maintains information about the owner name and the current balance and supports services that allow the client code to access the values of object data members and carry out deposits and withdrawals In addition... more than once In general, C++ is against that, and a class may explicitly appear only once in a derivation list for a derived class class B { public: int m; } ; class Derived: public B, public B { } ; // syntax error This does not make much sense, and it is outlawed as a syntax error However, the same class can appear multiple times in an inheritance hierarchy Different base classes may have... services, and this is all that is needed¡Xwithout the complexity of multiple inheritance Summary In this chapter, we looked at advanced uses of inheritance They all rotate around the fact that base classes and derived classes have some capabilities in common; hence, objects of one class can be used instead of objects of another class, at least in some circumstances You saw that it is always safe to use an... recommend that you use multiple inheritance with caution The advantages are few and complications are many There are always ways to adequately support the client code without using multiple inheritance Multiple Inheritance: Access Rules With multiple inheritance, a derived class inherits all data members of all bases and all member functions of all bases The space that an object of the derived class occupies... base class of // protected data // general // allocate heap space exit(0); } // initialize data fields // common for both // protect data from // pop responsibility up // increment I am going to create an array of Account pointers, create Account objects dynamically, initialize them, search for an account that belongs to a given owner, and deposit and withdraw funds Again, for simplicity of the example,... you would hear that yes, basically this is correct, but there are quite a few exceptions that make a NOW account different from both a savings account and a checking account This means that the benefits of easily merging basic characteristics are offset by the drawbacks of suppressing features that do not fit For the C++ iostream class library, the use of multiple inheritance to merge characteristics... inheritance sparingly if at all With virtual functions, it is a different story They are immensely popular in C++ programming They are so popular that I am afraid it is not politically correct to argue against their use Remember, however, that the virtual function mechanism is fragile You have to use public inheritance You have to use the same name in all classes in your inheritance hierarchy You have . error syntax error crash available Redefined in Derived (non-virtual) not available not available crash available Redefined in Derived (virtual) not available dynamic binding crash available Pure. virtual functions that makes a class an abstract (or partial) class. In addition to the lack of instantiated objects, an abstract class is required to have at least one file://///Administrator/General%20English%20Learning/it2002-7-6 /core. htm. derived class has yet another data member (a dynamically allocated character array). The Derived class should provide a constructor with three parameters so that it can pass data to its B1 and B2