I l@ve RuBoard Previous Section Next Section

5.6 Using an Inheritance Hierarchy

Let's presume we've defined the five other numeric sequence classes (Pell, Lucas, Square, Triangular, and Pentagonal) in the same manner as the Fibonacci class. We now have a two-level inheritance hierarchy: an abstract num_sequence base class and the six inheriting derived classes. How might we use them?

Here is a simple display() function whose second parameter is ns, a const reference to a num_sequence object.



inline void display( ostream &os, 


                     const num_sequence &ns, int pos ) 


{ 


   os << "The element at position " 


      << pos            << " for the " 


      << ns.what_am_i() << " sequence is " 


      << ns.elem( pos ) << endl; 


} 

Within display(), we call the two virtual functions what_am_i() and elem(). Which instances of these functions are invoked? We cannot say for certain. We know that ns does not refer to an actual num_sequence class object but rather to an object of a class derived from num_sequence. The two virtual function calls are resolved at run-time based on the type of the class object ns refers to. For example, in the following small program I define an object of each derived class in turn and pass it to display():



int main() 


{ 


   const int pos = 8; 





   Fibonacci fib; 


   display( cout, fib, pos ); 





   Pell pell; 


   display( cout, pell, pos ); 





   Lucas lucas; 


   display( cout, lucas, pos ); 





   Triangular trian; 


   display( cout, trian, pos ); 





   Square square; 


   display( cout, square, pos ); 





   Pentagonal penta; 


   display( cout, penta, pos ); 


} 

When compiled and executed, this program generates the following output:



The element at position 8 for the Fibonacci sequence is 21 


The element at position 8 for the Pell sequence is 408 


The element at position 8 for the Lucas sequence is 47 


The element at position 8 for the Triangular sequence is 36 


The element at position 8 for the Square sequence is 64 


The element at position 8 for the Pentagonal sequence is 92 

Notice that this program duplicates the output of our earlier program in Section 4.10. The design of the num_sequence class, however, has changed significantly. The machinery to set, keep track of, and reset the numeric sequence type in the earlier design has been removed. The language provides that support implicitly through the inheritance and the virtual function mechanisms. This object-oriented design is also considerably simpler both to modify and to extend. Unlike our earlier design, adding or removing an existing numeric sequence class requires no invasive changes.

Recall that in our design of the num_sequence base class, we also provided an overloaded instance of the output operator:



ostream& operator<<( ostream &os, const num_sequence &ns ) 


                   { return ns.print( os ); } 

Because print() is a virtual function, the output operator works with each derived class instance. For example, here's a small program in which I define an object of each numeric sequence and then direct each in turn to the output operator:



int main() 


{ 


   Fibonacci  fib( 8 ); 


   Pell       pell( 6, 4 ); 


   Lucas      lucas( 10, 7 ); 


   Triangular trian( 12 ); 


   Square     square( 6, 6 ); 


   Pentagonal penta( 8 ); 


   cout << "fib: "    << fib    << '\n' 


        << "pell: "   << pell   << '\n' 


        << "lucas: "  << lucas  << '\n' 


        << "trian: "  << trian  << '\n' 


        << "square: " << square << '\n' 


        << "penta: "  << penta  << endl; 


}; 

When compiled and executed, the program generates the following output:



fib: ( 1 , 8 ) 1 1 2 3 5 8 13 21 


pell: ( 4 , 6 ) 12 29 70 169 408 985 


lucas: ( 7 , 10 ) 29 47 76 123 199 322 521 843 1364 2207 


trian: ( 1 , 12 ) 1 3 6 10 15 21 28 36 45 55 66 78 


square: ( 6 , 6 ) 36 49 64 81 100 121 


penta: ( 1 , 8 ) 1 5 12 22 35 51 70 92 
    I l@ve RuBoard Previous Section Next Section