I l@ve RuBoard Previous Section Next Section

5.9 Defining a Derived Class Virtual Function

When we define a derived class, we must decide whether to override or inherit each of the base class virtual functions. If we inherit a pure virtual function, the derived class is then also considered an abstract class, and no independent objects of the class can be defined.

If we choose to override the base class instance, the prototype of the derived class instance must match the base class protoype exactly: the parameter list and the return type and whether the virtual function is const or non-const. For example, the following definition of Fibonacci::what_am_i() is not quite right:



class Fibonacci : public num_sequence { 


public: 


   virtual const char* what_am_i() // not quite right ... 


           { return "Fibonacci"; } 


   // ... 


}; 

Although the Fibonacci instance of what_am_i() is not quite right, it is not wrong, and that's where the confusion comes in. When I compile it under the Intel C++ compiler, I get the following warning message:



warning #653: "const char *Fibonacci::what_am_i()" 


     does not match "num_sequence::what_am_i" 


     -- virtual function override intended? 

What is this telling us? It is saying that the derived class declaration of what_am_i() does not exactly match the base class declaration. The base class instance is declared to be a const member function. The derived class instance is a non-const member function. Is this discepancy significant? Unfortunately, it is. Here's a simple illustration:



class num_sequence { 


public: 


   virtual const char* what_am_i() const 


           { return "num_sequence\n"; } 


}; 





class Fibonacci : public num_sequence { 


public: 


   virtual const char *what_am_i() 


           { return "Fibonacci\n"; } 


}; 





int main() 


{ 


  Fibonacci b; 


  num_sequence p; 





  // expect this to generate: Fibonacci 


  num_sequence *pp = &b; 


  cout << pp->what_am_i(); 





  cout << b.what_am_i(); 


  return 0; 


} 

The expected output of this program is two Fibonacci strings. When we compile and execute the program, however, it generates the following unexpected output:



num_sequence 


Fibonacci 

The warning message is telling us that the derived class instance is not treated as overriding the base class instance because the two instances do not match exactly. This is a common beginner error and usually proves baffling. That's why I've spent so much time explaining it. The fix is simple; understanding why it is needed is less simple.

Here is a second incorrect redeclaration of what_am_i(). In this case, our return type does not match the return type of the num_sequence instance exactly:



class Fibonacci : public num_sequence { 


public: 


   // incorrect declaration! 


   // base class instance returns const char*, not char* 


   virtual char* what_am_i(){ return "Fibonacci"; } 


   // ... 


}; 

The num_sequence instance of what_am_i() returns a const char*. The Fibonacci instance returns a char*. This is simply incorrect and results in a compile-time error.

There is one exception to the exact return type rule: If the base class virtual function returns some base class type, typically a pointer or reference,



class num_sequence { 


public: 


    // derived class instance of clone() can return a 


    // pointer to any class derived from num_sequence 


    virtual num_sequence *clone()=0; 





    // ... 


}; 

the derived class instance can return a type that is derived from the base class return type:



class Fibonacci : public num_sequence { 


public: 


    // ok: Fibonacci is derived from num_sequence 


    // the virtual keyword in the derived class is optional 


    Fibonacci *clone(){ return new Fibonacci( *this ); } 





    // ... 


}; 

When we are overriding a base class virtual function in the derived class, the virtual keyword is optional. The function is identified as overriding the base class instance based on a comparison of the two function prototypes.

Static Resolution of a Virtual Function

There are two circumstances under which the virtual mechanism does not behave as we expect: (1) within the base class constructor and destructor and (2) when we use a base class object rather than a base class pointer or reference.

When we construct a derived class object, the base class constructor is invoked first. What happens if within the base class constructor a virtual function is called? Should the derived class instance be invoked?

The problem is that the derived class data members are not yet initialized. If the derived class instance of the virtual function is invoked, it is likely to access data members that are uninitialized, and that is not a good thing.

For that reason, within the base class constructor, the derived class virtual functions are never invoked! Within the num_sequence constructor, for example, a call of what_am_i() resolves to the num_sequence instance even if a Fibonacci class object is being defined. The same holds true for virtual functions invoked within the base class destructor.

Consider the following program fragment, which uses the LibMat and AudioBook classes of Section 5.1. Recall that print() is a virtual function of that class hierarchy.



void print( LibMat object, 


            const LibMat *pointer, 


            const LibMat &reference ) 


{ 


    // always invokes LibMat::print() 


    object.print(); 





    // always resolved through the virtual mechanism 


    // we don't know what instance of print() this invokes 


    pointer->print(); 


    reference.print(); 


} 

Polymorphism requires indirection in order to make possible the representation of multiple types within a single object. In C++, only pointers and references of the base class support object-oriented programming.

When we declare an actual object of the base class, such as the first parameter of print(), we allocate enough memory to represent an actual base class object. If we later pass in a derived class object, there simply isn't enough memory available to store the additional derived class data members. For example, when we pass an AudioBook object to print(), as in the following,



int main() 


{ 


   AudioBook iWish( "Her Pride of 10", 


                    "Stanley Lippman", "Jeremy Irons" ); 


   gen_elems( iWish, &iWish, iWish ); 


   // ... 


} 

only the base class LibMat portion of iWish can be copied into the memory reserved for object; Tthe Book and AudioBook subobjects are sliced off. pointer and reference are initialized simply with the address of the original iWish object. This is why they can address the complete AudioBook object. (For a more in-depth discussion, see Section 1.3 of [LIPPMAN96a].)

    I l@ve RuBoard Previous Section Next Section