Solution

I l @ ve RuBoard

graphics/bulb.gif

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 classic diamond-shaped inheritance graph shape shown in Figure 4.

Figure 4. The Dreaded Diamond of Death (if inheritance from B is virtual).

graphics/04fig01.gif

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

This situation introduces the need for an extra feature in C++: virtual inheritance. The question at issue is: Does the programmer want D to have one B base subobject or two? If the answer is one, then B should be a virtual base class and Figure 4 becomes the Dreaded Diamond of Death. If the answer is two, then 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 [Stroustrup00] or [Meyers97] Item 43.

Guideline

graphics/guideline.gif

Avoid multiple inheritance from more than one non-protocol class (a protocol class means an abstract base class, or ABC, composed entirely of pure virtual functions with no data).


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.

So here we have this wonderful feature called MI. The question is, or at least was, is this a Good Thing? [1]

[1] In part, the topic for this Item was inspired by events at the SQL standards meeting in June 1998, when multiple inheritance was removed from the ANSI SQL99 draft standard. (Those of you who are interested in databases may see a revised form of MI resurrected in SQL4, if/when we get that far.) This was mainly done because the proposed multiple inheritance specification had technical difficulties, and in order to align with languages like Java that do not support true multiple inheritance. Still, just sitting there and listening to people discussing the merits and demerits of multiple inheritance at such a relatively late date was intriguing. It's something we haven't done much in the C++ world since the formative years, and it made me reminisce aloud about extended newsgroup flame wars from years ago (and some more recent than that) with subject lines like "MI is evil!!!"

In short, there are people who think that MI is just a bad idea that should be avoided at all costs. That's not true. Yes, if it's used thoughtlessly, MI can incur unnecessary coupling and complexity. But so does any kind of misused inheritance (see Exceptional C++ [Sutter00] Item 24), and I think we agree that doesn't make inheritance a Bad Thing. And, yes, any program can be written without resorting to MI, but for that matter, any program can be written without using inheritance at all. In fact, any program can be written in assembler. That doesn't mean it's necessarily a good idea or even something you would feel happy about doing.

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

So when is MI appropriate? In short, only when each inheritance, taken individually, is appropriate. Exceptional C++ Item 24 presented a fairly exhaustive list of when to use inheritance. Most appropriate real-world uses of MI fall into one of three categories.

  1. Combining modules or libraries. I'm citing this point first for a reason, illustrated again below. Many classes are designed to be base classes ”that is, to use them you are intended to inherit from them. The natural consequence is the question:

    What if you want to write a class that extends two libraries, and each library requires you to inherit from one of its classes?

    When you're facing this kind of situation, you usually don't have the option of changing the library code to avoid some of the inheritance. You probably purchased the library from a third-party vendor, or maybe it's a module produced by another project team inside your company. Either way, not only can't you change the code, but you may not even have the code! If so, then MI is necessary; there's no other (natural) way to do what you have to do, and using MI is perfectly legitimate .

    In practice, I've found that knowing how to use MI to combine vendor libraries is an essential technique that belongs in every C++ programmer's toolchest. Whether you end up using it frequently or not, you should definitely know about it and understand it.

  2. Protocol classes (interface classes). In C++, MI's best and safest use is to define protocol classes ”that is, classes composed of nothing but pure virtual functions. The absence of data members in the base class avoids outright MI's more famous complexities.

    Interestingly, different languages/models support this kind of MI through non-inheritance mechanisms. Two examples are Java and COM. Strictly speaking, Java has multiple inheritance, but constrains inheritance of implementation to single inheritance. A Java class can implement multiple "interfaces," where an interface is very similar to a C++ pure abstract base class without data members. COM does not include the concept of inheritance per se (although that's the usual implementation technique for COM objects written in C++), but it likewise has a notion of a composition of interfaces, and COM interfaces resemble a combination of Java interfaces and C++ templates.

  3. Ease of (polymorphic) use. Using inheritance to let other code use a derived object wherever a base is expected is a powerful concept. In some cases, it can be very useful to let the same derived object be used in the place of several kinds of bases, and that's where MI comes in. For a good example of this, see [Stroustrup00] section 14.2.2, which demonstrates an MI-based design for exception classes in which a most-derived exception class may have a polymorphic Is-A relationship, with multiple direct base classes.

    Note that #3 overlaps greatly with #1 and #2. It's frequently useful to do #3 at the same time as one of the others, and for the same reasons.

    One more thing to think about: Don't forget that sometimes it's not necessary to just inherit from two different base classes, but to inherit from each one for a different reason. "Polymorphic LSP Is-A public inheritance" isn't the only game in town; there are many other possible reasons to use inheritance. 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 .

I l @ ve RuBoard


More Exceptional C++
More Exceptional C++: 40 New Engineering Puzzles, Programming Problems, and Solutions
ISBN: 020170434X
EAN: 2147483647
Year: 2001
Pages: 118
Authors: Herb Sutter

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net