Monday, October 19, 2009

22.7. Multiple Inheritance



22.7. Multiple Inheritance


In Chapters
12 and 13, 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.



Good Programming Practice 22.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 22.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. 22.7, Fig. 22.8, Fig. 22.9, Fig. 22.10, Fig. 22.11). Class
Base1 (Fig.
22.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.











Fig. 22.7. Demonstrating multiple
inheritance—Base1.h.


 

 1   // Fig. 22.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




Class Base2 (Fig. 22.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.











Fig. 22.8. Demonstrating multiple
inheritance—Base2.h.


 

 1   // Fig. 22.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




Class Derived (Figs. 22.9–22.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.











Fig. 22.9. Demonstrating multiple
inheritance—Derived.h.


 

 1   // Fig. 22.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




To indicate multiple inheritance we follow the colon
(:) after class Derived with a comma-separated list of base
classes (line 14). In Fig. 22.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.











Fig. 22.10.
Demonstrating multiple inheritance—Derived.cpp.


 

 1   // Fig. 22.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<<




The overloaded stream insertion operator (Fig. 22.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 us examine the main
function (Fig.
22.11) that tests the classes in Figs. 22.7–22.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.













Fig. 22.11. Demonstrating multiple
inheritance.


 

 1   // Fig. 22.11: fig22_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 = 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 << "\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 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



Lines 18–20 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 getData is called. In
line 19, the compiler knows that base2 is an object of class
Base2, so class Base2's 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 24–27 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.


 


No comments:

Post a Comment