GotW #37

Home Blog Talks Books & Articles Training & Consulting

On the
blog
RSS feed November 4: Other Concurrency Sessions at PDC
November 3
: PDC'09: Tutorial & Panel
October 26: Hoare on Testing
October 23
: Deprecating export Considered for ISO C++0x

This is the original GotW problem and solution substantially as posted to Usenet. See the book More Exceptional C++ (Addison-Wesley, 2002) for the most current solution to this GotW issue. The solutions in the book have been revised and expanded since their initial appearance in GotW. The book versions also incorporate corrections, new material, and conformance to the final ANSI/ISO C++ standard.

Multiple Inheritance - Part I
Difficulty: 6 / 10

Some languages, including the emerging SQL3 standard, continue to struggle with the decision of whether to support single or multiple inheritance. This GotW invites you to consider the issues.

Problem

JG Question

1. What is multiple inheritance (MI), and what extra possibilities or complications does allowing MI introduce into C++?

Guru Question

2. Is MI ever necessary? If yes, show as many different situations as you can and argue why MI should be in a language. If no, argue why SI (possibly combined with Java-style interfaces) is equal or superior and why MI should not be in a language.

[NOTE: This GotW is not intended to resurrect long-dead arguments about what C++ should do. It is appropriate to consider the multiple inheritance (MI) issue now, however, because another popular language is currently facing the same decision: Less than two weeks ago in Portland OR, the ANSI SQL3 committee voted to remove MI as a feature of standard OO/ORDBMS. The corresponding ISO committee, meeting in Australia later this month, will consider the same paper and is likely to follow suit. If it goes there as it did at our ANSI meeting, SQL3 will include only SI (without even the compromise of Java-style interfaces). -hps]

Solution

1. What is multiple inheritance (MI), and what extra possibilities or complications does allowing MI introduce into C++?

Very briefly:

MI means the ability to inherit from more than a single direct base class. For example:

    class Derived : public Base1, private Base2
    {
      //...
    };

Allowing MI introduces the possibility that a class may have the same (direct or indirect) base class appear more than once as an ancestor. A simple example of this is the "Diamond of Death" shape:

        B
       / \
     C1   C2
       \ /
        D

Here B is an indirect base class of D twice, once via C1 and once via C2.

This situation introduces the need to an extra feature in C++: virtual inheritance. Does the programmer want D to have one B subobject, or two? If one, B should be a virtual base class; if two, B should be a normal (nonvirtual) base class.

Finally, the main complication of virtual base classes is that they must be initialized directly by the most-derived class. For more information on this and other aspects of MI, see a good text like C++PL3 or Meyers' "Effective C++" books.

 

2. Is MI ever necessary?

Short answer: No feature is strictly "necessary" insofar as any program can be written in assembler (or lower). However, just as most people would rather not code their own virtual function mechanism in plain C, in some cases not having MI requires painful workarounds.

If yes, show as many different situations as you can and argue why MI should be in a language. If no, argue why SI (possibly combined with Java-style interfaces) is equal or superior and why MI should not be in a language.

Short answer: Yes and no.

Like any tool, MI should be used carefully. Using MI always adds complexity (see JG question above), but there are situations when it is still simpler and more maintainable than the alternatives. As some have put it: "You only need MI rarely, but when you need it you REALLY need it."

There are many situations where MI is a convenient and appropriate tool. I'll just cover three (in fact, most uses fall into these three categories):

1. Interface Classes (Pure Abstract Base Classes)

In C++, MI's best and safest use is to define interface classes, that is, classes composed of nothing but pure virtual functions. In particular, it's the absence of data members in the base class that avoids MI's more famous complexities.

Interestingly, different languages/models support this kind of "MI" through non-inheritance mechanisms. Two examples are Java and COM: Java has only SI, but supports the notion that a class can implement multiple "interfaces" where the interfaces are very similar to C++ pure ABCs. COM does not include inheritance but likewise has a notion of composition of interface, and this model is similar to a combination of Java interfaces and C++ templates.

2. Combining Modules/Libraries

Many classes are designed to be base classes; that is, to use them you are intended to inherit from them. The natural consequence: What if you want to write a class that extends two libraries, and you are required to inherit from a class in each? Because you usually don't have the option of changing the library code (if you purchased the library from a third-party vendor, or it is a module produced by another project team inside your company), MI is necessary.

3. Ease of (Polymorphic) Use

There are examples where allowing MI greatly simplifies using the same object polymorphically in different ways. One good example is found in C++PL3 14.2.2 which demonstrates an MI-based design for exception classes, where a most-derived exception class may have a polymorphic IS-A relationship with multiple direct base classes.

Note that #1 overlaps greatly with #3.

Finally, don't forget that "polymorphic LSP IS-A public inheritance" isn't the only game in town; there are many other possible reasons to use inheritance. The result is that sometimes it's not just necessary to inherit from multiple base classes, but to do so from each one for different reasons. For example, a class may need to inherit privately from base class A to gain access to protected members of class A, but at the same time inherit publicly from base class B to polymorphically implement a virtual function of class B.

Copyright 2009 Herb Sutter