In Chapters 11 and 12, 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 class—a 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.
Software Engineering Observation 21.3
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 (Figs. 21.7–21.11). Class Base1
(Fig. 21.7) contains one protected int
data member—value
(line 20), a constructor (lines 10–13) that sets value
and public
member function getData
(lines 15–18) that returns value
.
1 // Fig. 21.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 : value( parameterValue )
12 {
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
Class Base2
(Fig. 21.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
.
1 // Fig. 21.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 : letter( characterData )
12 {
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
Class Derived
(Figs. 21.9–21.10) inherits from both class Base1
and class Base2
through multiple inheritance. Class Derived
has a private
data member of type double
named real
(Fig. 21.9, line 20), a constructor to initialize all the data of class Derived
and a public
member function getReal
that returns the value of double
variable real
.
1 // Fig. 21.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 <iostream>
8 #include "Base1.h"
9 #include "Base2.h"
10 using namespace std;
11
12 // class Derived definition
13 class Derived : public Base1, public Base2
14 {
15 friend ostream &operator<<( ostream &, const Derived & );
16 public:
17 Derived( int, char, double );
18 double getReal() const;
19 private:
20 double real; // derived class's private data
21 }; // end class Derived
22
23 #endif // DERIVED_H
1 // Fig. 21.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<<
To indicate multiple inheritance (in Fig. 21.9) we follow the colon (:
) after class Derived
with a comma-separated list of base classes (line 13). In Fig. 21.10, notice that constructor Derived
explicitly calls base-class constructors for each of its base classes—Base1
and Base2
—using 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. 21.10, lines 18–23) uses its second parameter—a reference to a Derived
object—to 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’s examine the main
function (Fig. 21.11) that tests the classes in Figs. 21.7–21.10. Line 11 creates Base1
object base1
and initializes it to the int
value 10
. Line 12 creates Base2
object base2
and initializes it to the char
value 'Z'
. Line 13 creates Derived
object derived
and initializes it to contain the int
value 7
, the char
value 'A'
and the double
value 3.5
.
1 // Fig. 21.11: fig21_11.cpp
2 // Driver for multiple-inheritance example.
3 #include <iostream>
4 #include "Base1.h"
5 #include "Base2.h"
6 #include "Derived.h"
7 using namespace std;
8
9 int main()
10 {
11 Base1 base1( 10 ); // create Base1 object
12 Base2 base2( 'Z' ); // create Base2 object
13 Derived derived( 7, 'A', 3.5 ); // create Derived object
14
15 // print data members of base-class objects
16 cout << "Object base1 contains integer " << base1.getData()
17 << "
Object base2 contains character " << base2.getData()
18 << "
Object derived contains:
" << derived << "
";
19
20 // print data members of derived-class object
21 // scope resolution operator resolves getData ambiguity
22 cout << "Data members of Derived can be accessed individually:"
23 << "
Integer: " << derived.Base1::getData()
24 << "
Character: " << derived.Base2::getData()
25 << "
Real number: " << derived.getReal() << "
";
26 cout << "Derived can be treated as an object of either base class:
";
27
28 // treat Derived as a Base1 object
29 Base1 *base1Ptr = &derived;
30 cout << "base1Ptr->getData() yields " << base1Ptr->getData() << '
';
31
32 // treat Derived as a Base2 object
33 Base2 *base2Ptr = &derived;
34 cout << "base2Ptr->getData() yields " << base2Ptr->getData() << endl;
35 } // 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
Lines 16–18 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 16, the compiler knows that base1
is an object of class Base1
, so class Base1
’s getData
is called. In line 17, the compiler knows that base2
is an object of class Base2
, so class Base2
’s getData
is called. Line 18 displays the contents of object derived
using the overloaded stream insertion operator.
13.59.32.1