Multiple Inheritance

In Chapters 9 and 10, we discussed single inheritance, in which each class is derived from exactly one base class. In C++, a class may be derived from more than one base classa technique known as multiple inheritance in which a derived class inherits the members of two or more base classes. This powerful capability encourages interesting forms of software reuse but can cause a variety of ambiguity problems. Multiple inheritance is a difficult concept that should be used only by experienced programmers. In fact, some of the problems associated with multiple inheritance are so subtle that newer programming languages, such as Java and C#, do not enable a class to derive from more than one base class.

Good Programming Practice 24.2

Multiple inheritance is a powerful capability when used properly. Multiple inheritance should be used when an "is a" relationship exists between a new type and two or more existing types (i.e., type A "is a" type B and type A "is a" type C).

Software Engineering Observation 24.4

Multiple inheritance can introduce complexity into a system. Great care is required in the design of a system to use multiple inheritance properly; it should not be used when single inheritance and/or composition will do the job.

A common problem with multiple inheritance is that each of the base classes might contain data members or member functions that have the same name. This can lead to ambiguity problems when you attempt to compile. Consider the multiple-inheritance example (Fig. 24.7, Fig. 24.8, Fig. 24.9, Fig. 24.10, Fig. 24.11). Class Base1 (Fig. 24.7) contains one protected int data membervalue (line 20), a constructor (lines 1013) that sets value and public member function getdata (lines 1518) that returns value.

Figure 24.7. Demonstrating multiple inheritanceBase1.h.

(This item is displayed on pages 1213 - 1214 in the print version)

1 // Fig. 24.7: Base1.h 2 // Definition of class Base1 3 #ifndef BASE1_H 4 #define BASE1_H 5 6 // class Base1 definition 7 class Base1 8 { 9 public: 10 Base1( int parameterValue ) 11 { 12 value = parameterValue; 13 } // end Base1 constructor 14 15 int getData() const 16 { 17 return value; 18 } // end function getData 19 protected: // accessible to derived classes 20 int value; // inherited by derived class 21 }; // end class Base1 22 23 #endif // BASE1_H

Figure 24.8. Demonstrating multiple inheritanceBase2.h.

(This item is displayed on page 1214 in the print version)

1 // Fig. 24.8: Base2.h 2 // Definition of class Base2 3 #ifndef BASE2_H 4 #define BASE2_H 5 6 // class Base2 definition 7 class Base2 8 { 9 public: 10 Base2( char characterData ) 11 { 12 letter = characterData; 13 } // end Base2 constructor 14 15 char getData() const 16 { 17 return letter; 18 } // end function getData 19 protected: // accessible to derived classes 20 char letter; // inherited by derived class 21 }; // end class Base2 22 23 #endif // BASE2_H

Figure 24.9. Demonstrating multiple inheritanceDerived.h.

(This item is displayed on page 1215 in the print version)

1 // Fig. 24.9: Derived.h 2 // Definition of class Derived which inherits 3 // multiple base classes (Base1 and Base2). 4 #ifndef DERIVED_H 5 #define DERIVED_H 6 7 #include 8 using std::ostream; 9 10 #include "Base1.h" 11 #include "Base2.h" 12 13 // class Derived definition 14 class Derived : public Base1, public Base2 15 { 16 friend ostream &operator<<( ostream &, const Derived & ); 17 public: 18 Derived( int, char, double ); 19 double getReal() const; 20 private: 21 double real; // derived class's private data 22 }; // end class Derived 23 24 #endif // DERIVED_H

Figure 24.10. Demonstrating multiple inheritanceDerived.cpp.

(This item is displayed on page 1216 in the print version)

1 // Fig. 24.10: Derived.cpp 2 // Member function definitions for class Derived 3 #include "Derived.h" 4 5 // constructor for Derived calls constructors for 6 // class Base1 and class Base2. 7 // use member initializers to call base-class constructors 8 Derived::Derived( int integer, char character, double double1 ) 9 : Base1( integer ), Base2( character ), real( double1 ) { } 10 11 // return real 12 double Derived::getReal() const 13 { 14 return real; 15 } // end function getReal 16 17 // display all data members of Derived 18 ostream &operator<<( ostream &output, const Derived &derived ) 19 { 20 output << " Integer: " << derived.value << " Character: " 21 << derived.letter << " Real number: " << derived.real; 22 return output; // enables cascaded calls 23 } // end operator<<

Figure 24.11. Demonstrating multiple inheritance.

(This item is displayed on pages 1216 - 1217 in the print version)

1 // Fig. 24.11: fig24_11.cpp 2 // Driver for multiple inheritance example. 3 #include 4 using std::cout; 5 using std::endl; 6 7 #include "Base1.h" 8 #include "Base2.h" 9 #include "Derived.h" 10 11 int main() 12 { 13 Base1 base1( 10 ), *base1Ptr = 0; // create Base1 object 14 Base2 base2( 'Z' ), *base2Ptr = 0; // create Base2 object 15 Derived derived( 7, 'A', 3.5 ); // create Derived object 16 17 // print data members of base-class objects 18 cout << "Object base1 contains integer " << base1.getData() 19 << " Object base2 contains character " << base2.getData() 20 << " Object derived contains: " << derived << " "; 21 22 // print data members of derived-class object 23 // scope resolution operator resolves getData ambiguity 24 cout << "Data members of Derived can be accessed individually:" 25 << " Integer: " << derived.Base1::getData() 26 << " Character: " << derived.Base2::getData() 27 << " Real number: " << derived.getReal() << " "; 28 cout << "Derived can be treated as an object of either base class: "; 29 30 // treat Derived as a Base1 object 31 base1Ptr = &derived; 32 cout << "base1Ptr->getData() yields " << base1Ptr->getData() << ' '; 33 34 // treat Derived as a Base2 object 35 base2Ptr = &derived; 36 cout << "base2Ptr->getData() yields " << base2Ptr->getData() << endl; 37 return 0; 38 } // end main  

Object base1 contains integer 10 Object base2 contains character Z Object derived contains: Integer: 7 Character: A Real number: 3.5 Data members of Derived can be accessed individually: Integer: 7 Character: A Real number: 3.5 Derived can be treated as an object of either base class: base1Ptr->getData() yields 7 base2Ptr->getData() yields A  


Class Base2 (Fig. 24.8) is similar to class Base1, except that its protected data is a char named letter (line 20). Like class Base1, Base2 has a public member function getdata, but this function returns the value of char data member letter.

Class Derived (Figs. 24.924.10) inherits from both class Base1 and class Base2 through multiple inheritance. Class Derived has a private data member of type double named real (line 21), a constructor to initialize all the data of class Derived and a public member function getreal that returns the value of double variable real.


Notice how straightforward it is to indicate multiple inheritance by following the colon (:) after class Derived with a comma-separated list of base classes (line 14). In Fig. 24.10, notice that constructor Derived explicitly calls base-class constructors for each of its base classesBase1 and Base2using the member-initializer syntax (line 9). The base-class constructors are called in the order that the inheritance is specified, not in the order in which their constructors are mentioned; also, if the base-class constructors are not explicitly called in the member-initializer list, their default constructors will be called implicitly.

The overloaded stream insertion operator (Fig. 24.10, lines 1823) uses its second parametera reference to a Derived objectto display a Derived object's data. This operator function is a friend of Derived, so operator<< can directly access all of class Derived's protected and private members, including the protected data member value (inherited from class Base1), protected data member letter (inherited from class Base2) and private data member real (declared in class Derived).

Now let us examine the main function (Fig. 24.11) that tests the classes in Figs. 24.724.10. Line 13 creates Base1 object base1 and initializes it to the int value 10, then creates the pointer base1Ptr and initializes it to the null pointer (i.e., 0). Line 14 creates Base2 object base2 and initializes it to the char value 'Z', then creates the pointer base2Ptr and initializes it to the null pointer. Line 15 creates Derived object derived and initializes it to contain the int value 7, the char value 'A' and the double value 3.5.


Lines 1820 display each object's data values. For objects base1 and base2, we invoke each object's geTData member function. Even though there are two geTData functions in this example, the calls are not ambiguous. In line 18, the compiler knows that base1 is an object of class Base1, so class Base1's version of geTData is called. In line 19, the compiler knows that base2 is an object of class Base2 so class Base2's version of geTData is called. Line 20 displays the contents of object derived using the overloaded stream insertion operator.

Resolving Ambiguity Issues That Arise When a Derived Class Inherits Member Functions of the Same Name from Multiple Base Classes

Lines 2427 output the contents of object derived again by using the get member functions of class Derived. However, there is an ambiguity problem, because this object contains two getdata functions, one inherited from class Base1 and one inherited from class Base2. This problem is easy to solve by using the binary scope resolution operator. The expression derived.Base1::getData() gets the value of the variable inherited from class Base1 (i.e., the int variable named value) and derived.Base2::getData() gets the value of the variable inherited from class Base2 (i.e., the char variable named letter). The double value in real is printed without ambiguity with the call derived.getReal()there are no other member functions with that name in the hierarchy.


Demonstrating the Is-A Relationships in Multiple Inheritance

The is-a relationships of single inheritance also apply in multiple-inheritance relationships. To demonstrate this, line 31 assigns the address of object derived to the Base1 pointer base1Ptr. This is allowed because an object of class Derived is an object of class Base1. Line 32 invokes Base1 member function getdata via base1Ptr to obtain the value of only the Base1 part of the object derived. Line 35 assigns the address of object derived to the Base2 pointer base2Ptr. This is allowed because an object of class Derived is an object of class Base2. Line 36 invokes Base2 member function geTData via base2Ptr to obtain the value of only the Base2 part of the object derived.

Категории