📄 ch12.htm
字号:
<HR></BLOCKQUOTE><H3 ALIGN="CENTER"><A NAME="Heading30"></A><FONT COLOR="#000077">Virtual Methods</FONT></H3><P>This chapter has emphasized the fact that a <TT>Dog</TT> object is a <TT>Mammal</TT>object. So far that has meant only that the <TT>Dog</TT> object has inherited theattributes (data) and capabilities (methods) of its base class. In C++ the is-a relationshipruns deeper than that, however.</P><P>C++ extends its polymorphism to allow pointers to base classes to be assignedto derived class objects. Thus, you can write</P><PRE><FONT COLOR="#0066FF">Mammal* pMammal = new Dog;</FONT></PRE><P>This creates a new <TT>Dog</TT> object on the heap and returns a pointer to thatobject, which it assigns to a pointer to <TT>Mammal</TT>. This is fine, because adog is a mammal.<BLOCKQUOTE> <P><HR><FONT COLOR="#000077"><B>NOTE:</B></FONT><B> </B>This is the essence of polymorphism. For example, you could create many different types of windows, including dialog boxes, scrollable windows, and list boxes, and give them each a virtual <TT>draw()</TT> method. By creating a pointer to a window and assigning dialog boxes and other derived types to that pointer, you can call <TT>draw()</TT> without regard to the actual run-time type of the object pointed to. The correct <TT>draw()</TT> function will be called. <HR></BLOCKQUOTE><P>You can then use this pointer to invoke any method on <TT>Mammal</TT>. What youwould like is for those methods that are overridden in <TT>Dog()</TT> to call thecorrect function. Virtual functions let you do that. Listing 12.8 illustrates howthis works, and what happens with non-virtual methods.</P><P><A NAME="Heading31"></A><FONT SIZE="4" COLOR="#000077"><B>Listing 12.8. Usingvirtual methods.</B></FONT></P><PRE><FONT COLOR="#0066FF">1: //Listing 12.8 Using virtual methods2:3: #include <iostream.h>4:5: class Mammal6: {7: public:8: Mammal():itsAge(1) { cout << "Mammal constructor...\n"; }9: ~Mammal() { cout << "Mammal destructor...\n"; }10: void Move() const { cout << "Mammal move one step\n"; }11: virtual void Speak() const { cout << "Mammal speak!\n"; }12: protected:13: int itsAge;14:15: };16:17: class Dog : public Mammal18: {19: public:20: Dog() { cout << "Dog Constructor...\n"; }21: ~Dog() { cout << "Dog destructor...\n"; }22: void WagTail() { cout << "Wagging Tail...\n"; }23: void Speak()const { cout << "Woof!\n"; }24: void Move()const { cout << "Dog moves 5 steps...\n"; }25: };26:27: int main()28: {29:30: Mammal *pDog = new Dog;31: pDog->Move();32: pDog->Speak();33:34: return 0;<TT>35: }</TT></FONT><FONT COLOR="#0066FF">Output: Mammal constructor...Dog Constructor...Mammal move one stepWoof!</FONT></PRE><P><FONT COLOR="#000077"><B>Analysis:</B></FONT><B> </B>On line 11, <TT>Mammal</TT>is provided a virtual method--<TT>speak()</TT>. The designer of this class therebysignals that she expects this class eventually to be another class's base type. Thederived class will probably want to override this function.</P><P>On line 30, a pointer to <TT>Mammal</TT> is created (<TT>pDog</TT>), but it isassigned the address of a new <TT>Dog</TT> object. Because a dog is a mammal, thisis a legal assignment. The pointer is then used to call the <TT>Move()</TT> function.Because the compiler knows <TT>pDog</TT> only to be a <TT>Mammal</TT>, it looks tothe <TT>Mammal</TT> object to find the <TT>Move()</TT> method.</P><P>On line 32, the pointer then calls the <TT>Speak()</TT> method. Because <TT>Speak()</TT>is virtual, the <TT>Speak()</TT> method overridden in <TT>Dog</TT> is invoked.</P><P>This is almost magical. As far as the calling function knew, it had a <TT>Mammal</TT>pointer, but here a method on <TT>Dog</TT> was called. In fact, if you had an arrayof pointers to <TT>Mammal</TT>, each of which pointed to a subclass of <TT>Mammal</TT>,you could call each in turn and the correct function would be called. Listing 12.9illustrates this idea.</P><P><A NAME="Heading33"></A><FONT SIZE="4" COLOR="#000077"><B>Listing 12.9. Multiplevirtual functions called in turn.</B></FONT></P><PRE><FONT COLOR="#0066FF">1: //Listing 12.9 Multiple virtual functions called in turn2:3: #include <iostream.h>4:5: class Mammal6: {7: public:8: Mammal():itsAge(1) { }9: ~Mammal() { }10: virtual void Speak() const { cout << "Mammal speak!\n"; }11: protected:12: int itsAge;13: };14:15: class Dog : public Mammal16: {17: public:18: void Speak()const { cout << "Woof!\n"; }19: };20:21:22: class Cat : public Mammal23: {24: public:25: void Speak()const { cout << "Meow!\n"; }26: };27:28:29: class Horse : public Mammal30: {31: public:32: void Speak()const { cout << "Winnie!\n"; }33: };34:35: class Pig : public Mammal36: {37: public:38: void Speak()const { cout << "Oink!\n"; }39: };40:41: int main()42: {43: Mammal* theArray[5];44: Mammal* ptr;45: int choice, i;46: for ( i = 0; i<5; i++)47: {48: cout << "(1)dog (2)cat (3)horse (4)pig: ";49: cin >> choice;50: switch (choice)51: {52: case 1: ptr = new Dog;53: break;54: case 2: ptr = new Cat;55: break;56: case 3: ptr = new Horse;57: break;58: case 4: ptr = new Pig;59: break;60: default: ptr = new Mammal;61: break;62: }63: theArray[i] = ptr;64: }65: for (i=0;i<5;i++)66: theArray[i]->Speak();67: return 0;<TT>68: }</TT></FONT><FONT COLOR="#0066FF">Output: (1)dog (2)cat (3)horse (4)pig: 1(1)dog (2)cat (3)horse (4)pig: 2(1)dog (2)cat (3)horse (4)pig: 3(1)dog (2)cat (3)horse (4)pig: 4(1)dog (2)cat (3)horse (4)pig: 5Woof!Meow!Winnie!Oink!Mammal speak!</FONT></PRE><P><FONT COLOR="#000077"><B>Analysis:</B></FONT><B> </B>This stripped-down program,which provides only the barest functionality to each class, illustrates virtual functionsin their purest form. Four classes are declared; <TT>Dog</TT>,<TT> Cat</TT>,<TT>Horse</TT>, and <TT>Pig</TT> are all derived from <TT>Mammal</TT>.</P><P>On line 10, <TT>Mammal</TT>'s <TT>Speak()</TT> function is declared to be virtual.On lines 18, 25, 32, and 38, the four derived classes override the implementationof <TT>Speak()</TT>.</P><P>The user is prompted to pick which objects to create, and the pointers are addedto the array on lines 46-64.<BLOCKQUOTE> <P><HR><FONT COLOR="#000077"><B>NOTE: </B></FONT>At compile time, it is impossible to know which objects will be created, and thus which <TT>Speak()</TT> methods will be invoked. The pointer <TT>ptr</TT> is bound to its object at runtime. This is called dynamic binding, or run-time binding, as opposed to static binding, or compile-time binding. <HR></BLOCKQUOTE><H4 ALIGN="CENTER"><A NAME="Heading35"></A><FONT COLOR="#000077">How Virtual FunctionsWork</FONT></H4><P>When a derived object, such as a <TT>Dog</TT> object, is created, first the constructorfor the base class is called and then the constructor for the derived class is called.Figure 12.2 shows what the <TT>Dog</TT> object looks like after it is created. Notethat the <TT>Mammal</TT> part of the object is contiguous in memory with the <TT>Dog</TT>part.<BR><BR><A NAME="Heading36"></A><A HREF="../art/ch12/12zcp02.jpg"><FONT COLOR="#000077">Figure12.2.</FONT></A><FONT COLOR="#000077"> </FONT><I>The <TT>Dog</TT> object after itis created.</I> <BR><BR>When a virtual function is created in an object, the object must keep track of thatfunction. Many compilers build a virtual function table, called a v-table. One ofthese is kept for each type, and each object of that type keeps a virtual table pointer(called a <TT>vptr</TT> or v-pointer), which points to that table.</P><P>While implementations vary, all compilers must accomplish the same thing, so youwon't be too wrong with this description.<BR><BR><A NAME="Heading37"></A><A HREF="../art/ch12/12zcp03.jpg"><FONT COLOR="#000077">Figure12.3.</FONT></A><FONT COLOR="#000077"> </FONT><I>The v-table of a <TT>Mammal</TT>.</I><BR><BR>Each object's <TT>vptr</TT> points to the v-table which, in turn, has a pointer toeach of the virtual functions. (Note, pointers to functions will be discussed indepth on Day 14, "Special Classes and Functions.") When the <TT>Mammal</TT>part of the <TT>Dog</TT> is created, the <TT>vptr</TT> is initialized to point tothe correct part of the v-table, as shown in Figure 12.3.<BR><BR><A NAME="Heading38"></A><A HREF="../art/ch12/12zcp04.jpg"><FONT COLOR="#000077">Figure12.4.</FONT></A><FONT COLOR="#000077"> </FONT>The v-table of a <TT>Dog</TT>.<BR><BR>When the <TT>Dog</TT> constructor is called, and the <TT>Dog</TT> part of this objectis added, the <TT>vptr</TT> is adjusted to point to the virtual function overrides(if any) in the <TT>Dog</TT> object (see Figure 12.4) .</P><P>When a pointer to a <TT>Mammal</TT> is used, the <TT>vptr</TT> continues to pointto the correct function, depending on the "real" type of the object. Thus,when <TT>Speak()</TT> is invoked, the correct function is invoked.<H4 ALIGN="CENTER"><A NAME="Heading39"></A><FONT COLOR="#000077">You Cant Get Therefrom Here</FONT></H4><P>If the <TT>Dog</TT> object had a method, <TT>WagTail()</TT>, which is not in the<TT>Mammal</TT>, you could not use the pointer to <TT>Mammal</TT> to access thatmethod (unless you cast it to be a pointer to <TT>Dog</TT>). Because <TT>WagTail()</TT>is not a virtual function, and because it is not in a <TT>Mammal</TT> object, youcan't get there without either a <TT>Dog</TT> object or a <TT>Dog</TT> pointer.</P><P>Although you can transform the <TT>Mammal</TT> pointer into a <TT>Dog</TT> pointer,there are usually far better and safer ways to call the <TT>WagTail()</TT> method.C++ frowns on explicit casts because they are error-prone. This subject will be addressedin depth when multiple inheritance is covered tomorrow, and again when templatesare covered on Day 20, "Exceptions and Error Handling."<H4 ALIGN="CENTER"><A NAME="Heading40"></A><FONT COLOR="#000077">Slicing</FONT></H4><P>Note that the virtual function magic operates only on pointers and references.Passing an object by value will not enable the virtual functions to be invoked. Listing12.10 illustrates this problem.</P><P><A NAME="Heading41"></A><FONT SIZE="4" COLOR="#000077"><B>Listing 12.10. Dataslicing when passing by value.</B></FONT></P><PRE><FONT COLOR="#0066FF">1: //Listing 12.10 Data slicing with passing by value2:3: #include <iostream.h>4:5: enum BOOL { FALSE, TRUE };6: class Mammal7: {8: public:9: Mammal():itsAge(1) { }10: ~Mammal() { }11: virtual void Speak() const { cout << "Mammal speak!\n"; }12: protected:13: int itsAge;14: };15:16: class Dog : public Mammal17: {18: public:19: void Speak()const { cout << "Woof!\n"; }20: };21:22: class Cat : public Mammal23: {24: public:25: void Speak()const { cout << "Meow!\n"; }26: };27:28 void ValueFunction (Mammal);29: void PtrFunction (Mammal*);30: void RefFunction (Mammal&);31: int main()32: {33: Mammal* ptr=0;34: int choice;35: while (1)36: {37: BOOL fQuit = FALSE;38: cout << "(1)dog (2)cat (0)Quit: ";39: cin >> choice;40: switch (choice)41: {42: case 0: fQuit = TRUE;43: break;44: case 1: ptr = new Dog;45: break;46: case 2: ptr = new Cat;47: break;48: default: ptr = new Mammal;49: break;50: }51: if (fQuit)52: break;53: PtrFunction(ptr);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -