<<

9. Table of Contents: 9. Multiple Inheritance...... 9-1 Multiple inheritance is where a class inherits features from more 9.1 Introduction to MI...... 9-2 than one parent class. In addition to providing the derived class 9.2 Introduction to C++ MI ...... 9-5 9.3 Disambiguating Members...... 9-6 with a wide set of features, this allows the resulting derived class 9.3.1 Disambiguating Member Functions...... 9-6 to behave as if it was either parent class when upcast! 9.3.2 Dominance ...... 9-7 Multiple inheritance, in its simplest form, is not hard to 9.3.3 Disambiguating Member Attributes...... 9-7 9.4 Upcasting In The Presence of MI...... 9-8 understand, use, or implement. But by its very nature, multiple 9.5 Diamonds and Virtual Base Classes ...... 9-9 inheritance can in practice get very messy, complex, and for 9.5.1 Diamond Inheritance Patterns...... 9-9 compiler writers complicated, inefficient, and bug prone to 9.5.2 Virtual Base Classes...... 9-10 implement. And in many cases, it has been used by 9.5.3 Multiple Triggering of Constructors...... 9-11 programmers and library vendors as an inappropriate mechanism 9.6 Summary ...... 9-12 for mixing together of features. It has thus got a poor reputation. 9.7 References ...... 9-14 It probably deserves this reputation as it is rarely necessary in languages that don’t lack other compensating features. Readings: [Optional] The sub-section titled Multiple Inheritance in Chapter 14 of [Prata2002].

Copyright 1997 by . Tront 9-1 Copyright 1997 by R. Tront 9-2 9.1 Introduction to MI classes, and in many cases is not the best way. As a result, many good OO languages do not have MI. Here is an example of multiple inheritance: • One of the most well known and widely used OO languages, , does not have MI. • Modula-3 does not have MI. WindowWith Menu WindowWithBorder • ADA95 does not have MI. There is a section of the design rational for ADA95 originally from [Taft95] that examines the major 3 reasons you might want to mix classes. It then shows how all three can be done more elegantly, appropriately, or efficiently using the other features of ADA95. To see it, browse http://wuarchive.wustl.edu/languages/ada/userdocs/ rat106/rat_cont.htm, and select section II.4.6. Unfortunately, MI is not often the best way to mix the features of two classes together. But if a language does not have other ways of merging the features of two classes together, MI was WindowWithMenuAndBorder sometimes added as a way to do this. For instance: • MI is used in the very OO language Eiffel, as that language As shown in [Prata95], it is easy and straight forward to declare lacks a good way to import another module’s features. In fact, and use multiple inheritance. Here is the basic C++ syntax: it has little concept of modules. Instead, it simply inherits from any and all classes to provide a new class with the features you class SingingWaiter : public Waiter, public Singer { want. //added attributes. //added methods. • C++ had the #include and linker facilities to import features //overridden methods. from other modules and classes into a program. But this alone }; is not adequate. MI was added to C++ in Version 2 of the That is not so hard to comprehend. The idea is that you want to language to provide more class-specific mixing of features. mix together the features of two classes into a resulting derived Unfortunately, this was done before the C++ community class. That is not so bad. appreciated other alternative language facilities that would provide this same functionality. One of these other alternatives MI was originally an academic curiosity that was recognized to was nested classes (i.e. member attributes which are classes nicely model the concept of an IS-A relationship which occurs themselves), which model a class ‘having’ features of several when an instance could be a member of several unrelated classes (rather than ‘IS-A”). With proper nested classes (that classes. Sometimes being able to model/mix two classes as were automatically constructed, copied, and destructed), a parents is very important. But MI is not the only way to mix two Copyright 1997 by R. Tront 9-3 Copyright 1997 by R. Tront 9-4 class could elegantly have aspects of several classes. In many 9.2 Introduction to C++ MI cases of MI, the subclass IS not A subclass of its parent, it only HAS a parent. e.g. Is a window a rectangle, or does it just The example shown above, and repeated here: HAVE a set of coordinates for specifying a rectangle. For a class SingingWaiter : public Waiter, public Singer { discussion of composition vs. (even single) inheritance, see the //added attributes. “Choosing Composition vs. Inheritance” sub-section of //added methods. Chapter 12 of [Eckel95]. Nested classes were not added until //overridden methods. Version 4 of the C++ language. }; • C++ also lacked generic/parameterized/template classes at the illustrates straight forward declaration of MI. time MI was added. These have been present in ADA since We have already covered how base classes are handled. 1983, but their mixing power seems to have been long unappreciated by the C++ community. The template feature • Their public methods can be invoked by clients of the derived was finally added to C++ also in Version 4. Note that class. templates are often more efficient in time and space than • Their attributes can be referred to if public. multiple inheritance. • Their constructors, operators, and destructors can be manually Now that C++ has nested classes and templates, even the chained to, and are even often automatically chained to. designer of C++, if I recall [Stroustrup94] correctly, wishes MI Basically, these same things happen to the multiple base classes had not been added. There are some people who feel MI should of an MI derived class. There is not a lot more to say about the be removed from C++. Unfortunately, this is impractical. simple application of MI. It is the problems and complications Though not a lot of MI code has been written, it would be of MI which need attention. terrible to change C++ so that version 3 and earlier syntax no longer worked. On the other hand, many major vendor container libraries, such as those from Microsoft and Borland, have recently been re- written. Templates are so superior to MI for implementing containers (e.g. container classes whose job was to store other class instances) that both the vendors and the users of these libraries haven’t minded the disruptive change. Unfortunately, these vendors now have to support both their old MI container libraries and the new templatized ones.

Copyright 1997 by R. Tront 9-5 Copyright 1997 by R. Tront 9-6 9.3 Disambiguating Members 9.3.2 Dominance Another way to avoid this problem is, as the derived class 9.3.1 Disambiguating Member Functions programmer, manually override both the init() members of the The first messy complication you run into with inheritance is bases, and define one specifically for MIClass. This actually inheriting from parents that both have a member function with introduces a third version of init()! But C++ will consider the the same name. (This can also occur if the inheritance is from third one introduced via overriding in the derived class as one parent and from a grandparent on the other side of the ‘dominant’, and thus: family.) As an example of the former, consider: myMIInstance.init(); class Base1 { will invoke MIClass::init(). public: int init(); You must be careful as a client programmer when dominant }; class Base2 { functions are present in a complex (virtual) inheritance public: hierarchy, as it easy to see a function called init() in an upper int init(); class and use it thinking it is the only one which might be }; invoked. In fact, you might be invoking a dominant one further class MIClass : public Base1, public Base2 { //added and overridden members. below that you had not noticed! }; 9.3.3 Disambiguating Member Attributes If a client of MIClass invokes the member function: Ambiguity can also occur if there are identically named myMIInstance.init(); attributes inherited from more than one ancestry. Normally the which of the two init() functions is run? compiler will force you to disambiguate using the scope resolution operator. You might ask why anyone would program three classes this way. But what if your Base1 were supplied by Microsoft, and Base2 by Borland, and you needed to mix the features of the two classes. Because you do not have access to either base classes’ code, you cannot modify the code to avoid this ambiguity. The compiler will normally detect this kind of ambiguity. You can specify which init() to run by using the scope resolution operator:

myMIInstance.Base1::init();

Copyright 1997 by R. Tront 9-7 Copyright 1997 by R. Tront 9-8 9.4 Upcasting In The Presence of MI 9.5 Diamonds and Virtual Base Classes You will recall that polymorphism was made possible because 9.5.1 Diamond Inheritance Patterns the base class member attributes occupied the first few elements of a derived class instance record in memory. But what if a class Another problem can occur with multiple inheritance when there has two base classes. They both can’t be first! is a diamond pattern somewhere in the inheritance hierarchy. e.g. class WindowWithMenuAndBorder having a common This is where the job of the compiler writer gets tough. ancestor called Window. e.g. iostreams: Generally you must (implicitly) upcast a pointer to a derived class to assign it to a pointer to a base class. When you do this in the presence of MI, the compiler has been programmed NOT ios to necessarily point at the start of the instance. Instead, it is ‘adjusted’ to point at the start of the region of memory representing the base class that is being upcast to. Thus, from the programmer’s point of view, upcasting to either parent magically still works. But this magic takes implementation effort, time, and space. istream ostream

iostream

This is the actual inheritance for iostreams (you can see the disruption removing MI from C++ would cause!). Diamond inheritance patterns cause several problems: a) If class ios had an attribute called ‘state’, then iostream instances would inherit two ‘state’ attributes, one indirectly from ios through istream, and one indirectly through ostream. This may be appropriate. Often, though, it is not.

Copyright 1997 by R. Tront 9-9 Copyright 1997 by R. Tront 9-10 b) In addition, if class ios had a member function changeState(), 9.5.2 Virtual Base Classes which of the two ‘states’ would it modify? c) If you wanted to upcast to the root class of the diamond, which of To make class iostream have only one ios, you have to have the two copies of the base class instance record within the bottom access to the source code for the istream and ostream classes. class record should the pointer be magically adjusted to point to? (This is not always possible, and thus very bad). You have to This also messes up polymorphism. add the keyword ‘virtual’ to the way the istream and ostream classes inherit from class ios. e.g. I believe that most (not all) of the time, you can get around the latter two problems by using the scope resolution operator twice, class istream : virtual public ios { or doing a manual upcast twice, as appropriate, to specify the }; route up the diamond that should be taken. class ostream : virtual public ios { }; Often, you don’t want two copies of the root in the diamond. class iostream : public istream, public ostream { The following section shows how to tell the C++ compiler not to }; make room for two copies. Notice the iostream class itself doesn’t have to inherit using the keyword virtual. This causes only one occurrence of an ios record to be contained within an iostream instance. The result is that problems a) through c) above are resolved (which is fine if you don’t want the bottom class to have 2 copies of the root class). You can see that virtual base classes have nothing to do with virtual functions or abstract base classes. Do not get the three mixed up! The keyword ‘virtual’ was used for this syntax only because the C/C++ community hates introducing new keywords that might break existing code because they conflict with a program’s variable or function or class names. C/C++’s namespaces are very polluted/full, making programmers very sensitive. This is partly their own fault for using so many global variables and function names (i.e. not restricting those that only need file scope to file scope using the ‘static’ keyword).

Copyright 1997 by R. Tront 9-11 Copyright 1997 by R. Tront 9-12 9.5.3 Multiple Triggering of Constructors 9.6 Summary Unfortunately, if there are two paths back to the root of the • Use MI very sparingly. The designer of C++ says in diamond, it’s possible for an explicitly-written constructor in [Stroustrup94] that it is often used for: the virtual base class to get run twice (which might wrongly - merging independent or almost independent hierarchies (e.g. increment some population count twice). To avoid this, display and task, to get display_task). triggering an explicit constructor from an initialization list up to - composition of class interfaces (i.e. member function signatures. virtual base class constructor is forbidden. If you do try to trigger e.g. istream and ostream to yield iostream for the client one from the intermediate classes, this will be disallowed. To programmer). run a particular explicit root class constructor, you must trigger it - merging of an abstract with an different from the initialization list of the constructor of the most derived implementation. class of the diamond. This is the only situation where an I’m not sure it is needed even in all the above cases. initialization list can contain the name of an indirect (e.g. • Use it only if you have read a lot more about it. For instance: grandparent) base class constructor. - The subsection titled “Avoiding MI” in Chapter 15 of [Eckel95] Note: If you don’t try to explicitly initialize the base class, it will - Section 14.5 and Chapters 10, 11, and 19 of [Meyers88] on be properly default constructed for you. The only reason you using the Eiffel language. Section 14.5 is about composition vs. would need to non-default construct it is if there was non-default inheritance. And Chapter 19 is all about MI vs. templates. constructor which had been explicitly written and which you - Chapter 8 of [Carroll95]. wanted triggered. - Magazine articles such as [Barton95]. - [Taft95] • Before you use it, ask yourself if an IS-A relationship really exist to each of the parents. Possibly one (or both!) could be accomplished with a nested component object. • Ask yourself if the two feature sets could be more appropriately mixed using the new template capability of Version 4. This is often the case with container classes. (Or when using Java, if the multiple interfaces feature is applicable. Java also saw no rational for multiple inheritance.) • Ask yourself whether you will need dynamic polymorphism. It is the major reason to use inheritance rather than other language features. If you don’t need polymorphism, you may not really need inheritance and therefore multiple inheritance. • Realize that single inheritance trees are hard enough to understand. For instance, in a big single inheritance tree, it is hard to find out the list of all the member functions that a Copyright 1997 by R. Tront 9-13 Copyright 1997 by R. Tront 9-14 derived class has, as most are documented in the ancestor’s 9.7 References code files. The situation gets worse because MI makes the relationships a connected graph, rather than a strict tree. [Barton95] “Two Faces of Polymorphism” by John Barton and • Realize that MI is slower, taking at best 5 indirect references to Lee Nackman, in C++ Report magazine, Nov.-Dec., 1995. invoke a (vs. 3 for single inheritance). [Carrol95] “Designing and Coding Re-usable C++” by Martin • Realize that MI take more space in each instance than just a 32 Carrol and Margaret Ellis, Addison-Wesley, 1995. bit pointer. In fact, the intermediate classes that virtually [Eckel95] “Thinking in C++” by Bruce Eckel, Prentice Hall, inherit from the root may each contain 3 pointers, and the most 1995. derived class could contain 5 pointers. It gets worse if a derived class has 3 or 4 parents! If someone offers you a job [Meyers88] “Object-Oriented Software Construction” by writing a C++ compiler, don’t take it. See the sub-section Bertrand Meyers, Prentice Hall, 1988. titled “Overhead” of Chapter 15 [Eckel95], or [Lippman96]. [Lippman96] “Inside the C++ Object Model” by Stanley • MI also take more code space for all the magic pointer Lippman, Addison-Wesley, 1996. adjustments, etc. [Stroustrup94] “The Design and Evolution of C++” by Bjarne Stroustrup, Addison-Wesley, 1994. [Taft95] “Multiple Inheritance in ADA 9X” by S. Tucker Taft, Ada Information Clearinghouse/Ada Joint Program Office/IIT Research Institute, 1995. To see it, browse the official ADA95 design rational at http://wuarchive.wustl.edu/languages/ada/ userdocs/rat106/rat_cont.htm, and select section II.4.6

Copyright 1997 by R. Tront 9-15 Copyright 1997 by R. Tront 9-16