Flylib.com

Books Software

 
 
 

Section 24.7. Multiple Inheritance


[Page 1213 ( continued )]

24.7. 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 member value (line 20), a constructor (lines 1013) that sets value and public member function getdata (lines 1518) that returns value .

Figure 24.7. Demonstrating multiple inheritance Base1.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 inheritance Base2.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 inheritance Derived.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

<iostream>
 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 inheritance Derived.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 <<

"\n  Character: "

21          << derived.letter <<

"\nReal 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

<iostream>
 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 =


;

// create Base1 object

14     Base2 base2(

'Z'

), *base2Ptr =


;

// 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        <<

"\nObject base2 contains character "

<< base2.getData()
20        <<

"\nObject derived contains:\n"

<< derived <<

"\n\n"

;
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        <<

"\n    Integer: "

<<

derived.Base1::getData()

26        <<

"\n  Character: "

<<

derived.Base2::getData()

27        <<

"\nReal number: "

<<

derived.getReal()

<<

"\n\n"

;
28     cout <<

"Derived can be treated as an object of either base class:\n"

;
29
30

// treat Derived as a Base1 object

31

base1Ptr = &derived;

32     cout <<

"base1Ptr->getData() yields "

<<

base1Ptr->getData()

<<

'\n'

;
33
34

// treat Derived as a Base2 object

35

base2Ptr = &derived;

36     cout <<

"base2Ptr->getData() yields "

<<

base2Ptr->getData()

<< endl;
37

return



;
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




[Page 1214]

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 .


[Page 1215]

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 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. 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 .


[Page 1217]

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.


[Page 1218]

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 .