In Section 24.7, we discussed multiple inheritance, the process by which one class inherits from two or more classes. Multiple inheritance is used, for example, in the C++ standard library to form class basic_iostream (Fig. 24.12).
Figure 24.12. Multiple inheritance to form class basic_iostream.
Class basic_ios is the base class for both basic_istream and basic_ostream, each of which is formed with single inheritance. Class basic_iostream inherits from both basic_istream and basic_ostream. This enables objects of class basic_iostream to provide the functionality of both basic_istreams and basic_ostreams. In multiple-inheritance hierarchies, the situation described in Fig. 24.12 is referred to as diamond inheritance.
Because classes basic_istream and basic_ostream each inherit from basic_ios, a potential problem exists for basic_iostream. Class basic_iostream could contain two copies of the members of class basic_iosone inherited via class basic_istream and one inherited via class basic_ostream). Such a situation would be ambiguous and would result in a compilation error, because the compiler would not know which version of the members from class basic_ios to use. Of course, basic_iostream does not really suffer from the problem we mentioned. In this section, you will see how using virtual base classes solves the problem of inheriting duplicate copies of an indirect base class.
Compilation Errors Produced When Ambiguity Arises in Diamond Inheritance
Figure 24.13 demonstrates the ambiguity that can occur in diamond inheritance. The program defines class Base (lines 913), which contains pure virtual function print (line 12). Classes DerivedOne (lines 1624) and DerivedTwo (lines 2735) each publicly inherit from class Base and override the print function. Class DerivedOne and class DerivedTwo each contain what the C++ standard refers to as a base-class subobjecti.e., the members of class Base in this example.
Figure 24.13. Attempting to call a multiply inherited function polymorphically.
(This item is displayed on pages 1219 - 1220 in the print version)
1 // Fig. 24.13: fig24_13.cpp 2 // Attempting to polymorphically call a function that is 3 // multiply inherited from two base classes. 4 #include 5 using std::cout; 6 using std::endl; 7 8 // class Base definition 9 class Base 10 { 11 public: 12 virtual void print() const = 0; // pure virtual 13 }; // end class Base 14 15 // class DerivedOne definition 16 class DerivedOne : public Base 17 { 18 public: 19 // override print function 20 void print() const 21 { 22 cout << "DerivedOne "; 23 } // end function print 24 }; // end class DerivedOne 25 26 // class DerivedTwo definition 27 class DerivedTwo : public Base 28 { 29 public: 30 // override print function 31 void print() const 32 { 33 cout << "DerivedTwo "; 34 } // end function print 35 }; // end class DerivedTwo 36 37 // class Multiple definition 38 class Multiple : public DerivedOne, public DerivedTwo 39 { 40 public: 41 // qualify which version of function print 42 void print() const 43 { 44 DerivedTwo::print(); 45 } // end function print 46 }; // end class Multiple 47 48 int main() 49 { 50 Multiple both; // instantiate Multiple object 51 DerivedOne one; // instantiate DerivedOne object 52 DerivedTwo two; // instantiate DerivedTwo object 53 Base *array[ 3 ]; // create array of base-class pointers 54 55 array[ 0 ] = &both; // ERROR--ambiguous 56 array[ 1 ] = &one; 57 array[ 2 ] = &two; 58 59 // polymorphically invoke print 60 for ( int i = 0; i < 3; i++ ) 61 array[ i ] -> print(); 62 63 return 0; 64 } // end main
|
Class Multiple (lines 3846) inherits from both classes DerivedOne and DerivedTwo. In class Multiple, function print is overridden to call DerivedTwo's print (line 44). Notice that we must qualify the print call with the class name DerivedTwo to specify which version of print to call.
Function main (lines 4864) declares objects of classes Multiple (line 50), DerivedOne (line 51) and DerivedTwo (line 52). Line 53 declares an array of Base * pointers. Each array element is initialized with the address of an object (lines 5557). An error occurs when the address of bothan object of class Multipleis assigned to array[ 0 ]. The object both actually contains two subobjects of type Base, so the compiler does not know which subobject the pointer array[ 0 ] should point to, and it generates a compilation error indicating an ambiguous conversion.
Eliminating Duplicate Subobjects with virtual Base-Class Inheritance
The problem of duplicate subobjects is resolved with virtual inheritance. When a base class is inherited as virtual, only one subobject will appear in the derived classa process called virtual base-class inheritance. Figure 24.14 revises the program of Fig. 24.13 to use a virtual base class.
Figure 24.14. Using virtual base classes.
(This item is displayed on pages 1220 - 1222 in the print version)
1 // Fig. 24.14: fig24_14.cpp 2 // Using virtual base classes. 3 #include 4 using std::cout; 5 using std::endl; 6 7 // class Base definition 8 class Base 9 { 10 public: 11 virtual void print() const = 0; // pure virtual 12 }; // end class Base 13 14 // class DerivedOne definition 15 class DerivedOne : virtual public Base 16 { 17 public: 18 // override print function 19 void print() const 20 { 21 cout << "DerivedOne "; 22 } // end function print 23 }; // end DerivedOne class 24 25 // class DerivedTwo definition 26 class DerivedTwo : virtual public Base 27 { 28 public: 29 // override print function 30 void print() const 31 { 32 cout << "DerivedTwo "; 33 } // end function print 34 }; // end DerivedTwo class 35 36 // class Multiple definition 37 class Multiple : public DerivedOne, public DerivedTwo 38 { 39 public: 40 // qualify which version of function print 41 void print() const 42 { 43 DerivedTwo::print(); 44 } // end function print 45 }; // end Multiple class 46 47 int main() 48 { 49 Multiple both; // instantiate Multiple object 50 DerivedOne one; // instantiate DerivedOne object 51 DerivedTwo two; // instantiate DerivedTwo object 52 53 // declare array of base-class pointers and initialize 54 // each element to a derived-class type 55 Base *array[ 3 ]; 56 array[ 0 ] = &both; 57 array[ 1 ] = &one; 58 array[ 2 ] = &two; 59 60 // polymorphically invoke function print 61 for ( int i = 0; i < 3; i++ ) 62 array[ i ]->print(); 63 64 return 0; 65 } // end main
|
The key change in the program is that classes DerivedOne (line 15) and DerivedTwo (line 26) each inherit from class Base by specifying virtual public Base. Since both of these classes inherit from Base, they each contain a Base subobject. The benefit of virtual inheritance is not clear until class Multiple inherits from both DerivedOne and DerivedTwo (line 37). Since each of the base classes used virtual inheritance to inherit class Base's members, the compiler ensures that only one subobject of type Base is inherited into class Multiple. This eliminates the ambiguity error generated by the compiler in Fig. 24.13. The compiler now allows the implicit conversion of the derived-class pointer (&both) to the base-class pointer array[ 0 ] at line 56 in main. The for statement at lines 6162 polymorphically calls print for each object.
Constructors in Multiple-Inheritance Hierarchies with virtual Base Classes
Implementing hierarchies with virtual base classes is simpler if default constructors are used for the base classes. The examples in Figs. 24.13 and 24.14 use compiler-generated default constructors. If a virtual base class provides a constructor that requires arguments, the implementation of the derived classes becomes more complicated, because the most derived class must explicitly invoke the virtual base class's constructor to initialize the members inherited from the virtual base class.
Software Engineering Observation 24.5
Providing a default constructor for virtual base classes simplifies hierarchy design. |
Additional Information on Multiple Inheritance
Multiple inheritance is a complex topic typically covered in more advanced C++ texts. The following URLs provide additional information about multiple inheritance.
cplus.about.com/library/weekly/aa121302a.htm
A tutorial on multiple inheritance with a detailed example.
cpptips.hyperformix.com/MultipleInher.html
Provides technical tips that explain several issues regarding multiple inheritance.
www.parashift.com/c++-faq-lite/multiple-inheritance.html
Part of the C++ FAQ Lite. Provides a detailed technical explanation of multiple inheritance and virtual inheritance.
Introduction to Computers, the Internet and World Wide Web
Introduction to C++ Programming
Introduction to Classes and Objects
Control Statements: Part 1
Control Statements: Part 2
Functions and an Introduction to Recursion
Arrays and Vectors
Pointers and Pointer-Based Strings
Classes: A Deeper Look, Part 1
Classes: A Deeper Look, Part 2
Operator Overloading; String and Array Objects
Object-Oriented Programming: Inheritance
Object-Oriented Programming: Polymorphism
Templates
Stream Input/Output
Exception Handling
File Processing
Class string and String Stream Processing
Web Programming
Searching and Sorting
Data Structures
Bits, Characters, C-Strings and structs
Standard Template Library (STL)
Other Topics
Appendix A. Operator Precedence and Associativity Chart
Appendix B. ASCII Character Set
Appendix C. Fundamental Types
Appendix D. Number Systems
Appendix E. C Legacy Code Topics
Appendix F. Preprocessor
Appendix G. ATM Case Study Code
Appendix H. UML 2: Additional Diagram Types
Appendix I. C++ Internet and Web Resources
Appendix J. Introduction to XHTML
Appendix K. XHTML Special Characters
Appendix L. Using the Visual Studio .NET Debugger
Appendix M. Using the GNU C++ Debugger
Bibliography