Uses & Abuses of Inh... Part 2

Home Blog Talks Books & Articles Training & Consulting

Prev
Up
Next

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 article substantially as first published. See the book Exceptional C++ (Addison-Wesley, 2000) for the most current version of this article. The versions in the book have been revised and expanded since their initial appearance in print. The book versions also incorporate corrections, new material, and conformance to the final ANSI/ISO C++ standard.

Uses and Abuses of Inheritance, Part 2

This article appeared in C++ Report, 11(1), January 1999.

 

This is the second of two columns about inheritance, and how to use it judiciously. In the previous column,[1] I focused on private and protected inheritance, showing how these are often abused to express IS-IMPLEMENTED-IN-TERMS-OF when plain old containment would do just as well or better, and along the way I detailed a pretty exhaustive list of the reasons to use nonpublic inheritance. Thanks to Astute Reader Alex Martelli for his followup remarks about two more situations where using inheritance instead of containment can be appropriate; I'll start this month's column by covering one of them. After that, I'll touch briefly on public inheritance, and then bring things together by covering some multiple inheritance issues and techniques.

Another Reason to Use Nonpublic Inheritance

Here is an addition to the previous column's list of reasons to use inheritance instead of containment:

o         We benefit substantially from the empty base class optimization. Sometimes the class you are IMPLEMENTING-IN-TERMS-OF may have no data members at all; that is, it's just a bundle of functions. In this case, there can be a space advantage to using inheritance instead of containment because of the empty base class optimization. In short, compilers are allowed to let an empty base subobject occupy zero space, whereas an empty member object must occupy nonzero space even if it doesn't have any data:

class B { /* ... functions only, no data ... */ };

// Containment: incurs some space overhead
//
class D {
  B b; // b must occupy at least one byte,
};     // even though B is an empty class

// Inheritance: can incur zero space overhead
//
class D : private B {
       // the B subobject need not occupy
};     // any space at all

For a detailed discussion of the empty base optimization, see Nathan Myers' excellent article on this topic in Dr. Dobb's Journal.[2]

Having said all that, let me end with a caution for the overzealous: Not all compilers actually perform the empty base class optimization, and even if they do you probably won't benefit significantly unless you know there will be many (say, tens of thousands) of these objects in your system. Unless the space savings are very important to your application, and you know that your compiler will actually perform the optimization, it would be a mistake to introduce the extra coupling of the stronger inheritance relationship instead of using simple containment.

The Most Important Thing to Know About Public Inheritance

There is only one point I want to stress about public inheritance, and if you follow this advice it will steer you clear of the most common abuses: Only use public inheritance to model true IS-A, as per the Liskov Substitution Principle (LSP).[3] That is, a publicly-derived class object should be able to be used in any context where the base class object could be used and still guarantee the same semantics.

In particular, following this rule will avoid two common pitfalls:

o         Never use public inheritance when non-public inheritance will do. Public inheritance should never be used to model IS-IMPLEMENTED-IN-TERMS-OF without true IS-A. This may seem obvious, but I've noticed that some programmers routinely make inheritance public out of habit. That's not a good idea, and this point is in the same spirit as my advice last time to never use inheritance (of any kind) when good old containment/membership will do. If the extra access and tighter coupling aren't needed, why use them? If a class relationship can be expressed in more than one way, use the weakest relationship that's practical.

o         Never use public inheritance to implement "almost IS-A." I've seen some programmers, even experienced ones, inherit publicly from a base and implement "most" of the overridden virtual functions in a way that preserved the semantics of the base class. In other words, in some cases using the Derived object as a Base would not behave quite the way that a reasonable Base client could expect. An example often cited by Robert Martin is the usually misguided idea of inheriting a Square class from a Rectangle class "because a square is a rectangle." That may be true in mathematics, but it's not necessarily true in classes. For example, say that the Rectangle class has a virtual SetWidth(int) function; then Square's implementation to set the width would also naturally set the height, so that the object remains square. Yet there may well exist code elsewhere in the system that works polymorphically with Rectangle objects, and would not expect that changing the width would also change the height -- after all, that's not true of Rectangles in general! This is a good example of public inheritance that would violate LSP, because the derived class does not deliver the same semantics as the base class.

Usually when I see people doing this kind of "almost IS-A" I'll try to point out to them that they're setting themselves up for trouble. After all, someone somewhere is bound to try to use derived objects polymorphically in one of the ways that would occasionally give unexpected results, right? "But it's okay," came one reply, "it's only a little bit incompatible, and I know that nobody uses Base-family objects that way [in that particular way that would be dangerous]." Well, being "a little bit incompatible" is a lot like being "a little bit pregnant"--now, I had no reason to doubt that the programmer was right, namely that no code then in the system would hit the dangerous differences; but I also had every reason to believe that someday, somewhere, a maintenance programmer was going to make a seemingly innocuous change, run afoul of the problem, and spend hours analyzing why the class was poorly designed and then spend additional days fixing it.

Don't be tempted. Just say no. If it doesn't behave like a Base, it's NOT-A Base, so don't derive and make it look like one.

Multiple Inheritance -- Nutshell Recap

This brings us to the main topic of this article -- multiple inheritance (MI). Very briefly: MI means the ability to inherit from more than one direct base class. For example:

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

Note that allowing MI also introduces the possibility that a derived class may have the same (direct or indirect) base class appear more than once as an ancestor. For example, in the above code, say that Base1 and Base2 were previously defined as:

class Base1 : public Base { /*...*/ };

class Base2 : public Base { /*...*/ };

Then Base appears twice as an indirect base class of Derived, once via Base1 and once via Base2. This situation introduces the need for an extra feature in C++: virtual inheritance. We need to be able to answer the question, "Does the programmer want Derived to have one Base subobject, or two?" If the answer is "one," then Base should be a virtual base class; if the answer is "two," Base should be a normal (nonvirtual) base class.

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 Bjarne Stroustrup's The C++ Programming Language, 3rd ed., or Scott Meyers' Effective C++ books.

Multiple Inheritance -- Boon, or Abomination?

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

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 the previous column), 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 be written without using inheritance at all, and in fact any program can be written in assembler.

So when is MI appropriate? In short, the answer is: Only when each inheritance, taken individually, is appropriate. In this column and the previous one I've tried to give a pretty exhaustive list of when to use inheritance. Specifically, most appropriate real-world uses of MI fall into one of three categories:

1.       Combining Modules/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, usually you 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 (Pure Abstract Base 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 only single inheritance (SI), but it also supports the notion that a 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 very 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 The C++ Programming Language, 3rd edition,[5] section 14.2.2, where Stroustrup 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 #2 overlaps greatly with #3. It's frequently useful to do both at once for the same reasons.

Here's one more thing to think about: Don't forget that sometimes it's not just necessary to 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.

Siamese Twin Functions

That said, I want to show you a minor pitfall that can come up with MI, and how to handle it effectively. Say that you're using two vendors' libraries in the same project. Vendor A provides the following base class BaseA:

class BaseA {
public:
  virtual int ReadBuf( const char* );
  /* ... */
};

The idea is that you're supposed to inherit from BaseA, probably overriding some virtual functions, because other parts of vendor A's library are written to expect objects they can use polymorphically as BaseAs. This is a common and normal practice, especially for extensible application frameworks, and there's nothing wrong with it...

... Nothing, that is, until you start to use Vendor B's library and discover, to your uneasy amazement:

class BaseB {
public:
  virtual int ReadBuf( const char* );
  /* ... */
};

"Well, that's rather a coincidence," you may think. Not only does vendor B, too, have a base class that you're expected to inherit from, but it happens to have a virtual function with exactly the same signature as one of the virtuals in BaseA. But BaseB's is supposed to do something completely different -- and that's the key point.

The problem becomes clear when you have to write a class that inherits from both BaseA and BaseB, perhaps because you need an object that can be used polymorphically by functions in both vendors' libraries. Here's a naive attempt at such a function:

// Attempt #1: Doesn't work
//
class Derived : public BaseA, public BaseB {
  /* ... */
  int ReadBuf( const char* );
      // overrides both BaseA::ReadBuf
      // and BaseB::ReadBuf
};

Here Derived::ReadBuf overrides both BaseA::ReadBuf and BaseB::ReadBuf. To see why that isn't good enough given our criteria, consider the following code:

// Counterexample 1: Why attempt #1 doesn't work
//
Derived d;
BaseA*  pba = d;
BaseB*  pbb = d;

pba->ReadBuf( "sample buffer" );
    // calls Derived::ReadBuf

pbb->ReadBuf( "sample buffer" );
    // calls Derived::ReadBuf

Do you see the problem? ReadBuf is virtual in both interfaces and it operates polymorphically just as we expect -- but the same function, Derived::ReadBuf, is invoked regardless of which interface is being used. Yet BaseA::ReadBuf and BaseB::ReadBuf have different semantics and are supposed to do different things, not the same thing. Further, Derived::ReadBuf has no way to tell whether it's being called through the BaseA interface or the BaseB interface (if either), so we can't just put an "if" inside Derived::ReadBuf to make it do something different depending on how it's called. That's lousy, but we're stuck with it.

"Oh, come on," you may be thinking. "This is an awfully contrived example, isn't it?" Actually, no, it's not. For example, Astute Reader John Kdllin of Microsoft reports that creating a class derived from both the IOleObject and IConnectionPoint COM interfaces (think of these as abstract base classes composed entirely of public virtual functions) becomes problematic, because: a) both interfaces have a member function with the signature virtual HRESULT Unadvise(unsigned long); and b) typically you have to override each Unadvise to do different things.

Stop a moment and think about the example above: How would you solve this problem? Is there any way that we can override the two inherited ReadBuf functions separately, so that we can perform different actions in each one, and the right actions get performed depending on whether outside code calls through the BaseA or BaseB interface? In short, how can we separate these twins?

How to Separate Siamese Twins

Fortunately, there is a fairly clean solution. The key to the problem is that the two overridable functions have exactly the same name and signature. The key to the solution, therefore, must lie in changing at least one function's signature, and the easiest part of the signature to change is the name.

How do you change a function's name? Through inheritance, of course! What's needed is an intermediate class that derives from the base class, declares a new virtual function, and overrides the inherited version to call the new function.

// Attempt #2: Correct
//
class BaseA2 : public BaseA {
public:
  virtual int BaseAReadBuf( const char* p ) = 0;
private:
  int ReadBuf( const char* p ) { // override inherited
    return BaseAReadBuf( p );    // to call new func
  }
};

class BaseB2 : public BaseB {
public:
  virtual int BaseBReadBuf( const char* p ) = 0;
private:
  int ReadBuf( const char* p ) { // override inherited
    return BaseBReadBuf( p );    // to call new func
  }
};

class Derived : public BaseA2, public BaseB2 {
  /* ... */

  int BaseAReadBuf( const char* );
      // overrides BaseA::ReadBuf indirectly
      // via BaseA2::BaseAReadBuf

  int BaseBReadBuf( const char* );
      // overrides BaseB::ReadBuf indirectly
      // via BaseB2::BaseBReadBuf
};

BaseA2 and BaseB2 may also need to duplicate constructors of BaseA and BaseB so that Derived can invoke them, but that's it. (Often a simpler way than duplicating the constructors in code is to have BaseA2 and BaseB2 derive virtually, so that Derived has direct access to the base constructors.) BaseA2 and BaseB2 are abstract classes, so they don't need to duplicate any other BaseA or BaseB functions or operators, such as assignment operators.

Now everything works as it should:

// Example 2: Why attempt #2 works
//
Derived d;
BaseA*  pba = d;
BaseB*  pbb = d;

pba->ReadBuf( "sample buffer" );
    // calls Derived::BaseAReadBuf

Further-derived classes only need to know that they must not further override ReadBuf itself. If they did, it would disable the renaming stubs that we installed in the intermediate classes.

Conclusion

Use inheritance wisely. If you can express a class relationship using containment alone, you should always prefer that. If you need inheritance but aren't modeling IS-A, use nonpublic inheritance. If you don't need the combinative power of multiple inheritance, prefer single inheritance.

Some people feel that "it just isn't OO unless you inherit," but that isn't really true. Large inheritance hierarchies in general, and deep ones in particular, are confusing to understand and therefore difficult to code and maintain. Use the simplest solution that works, and you'll be more likely to enjoy pleasant years of stable and maintainable code.

 

Notes

1. H. Sutter. "Uses and Abuses of Inheritance--Part I," (C++ Report, 10(9), October 1998).

2. N. Myers. "The Empty Base C++ Optimization," (Dr. Dobb's Journal, August 1997).

3. See www.objectmentor.com for several good papers describing LSP.

4. In part, the topic for this column was inspired by events at the SQL standards meeting this past June, because there MI was removed from the ANSI SQL3 draft standard (those of you who are interested in databases may see a revised form of MI resurrected in SQL4, once we get that far). This was mainly done because the proposed MI specification had technical difficulties, and to align with languages like Java that do not support true MI. Still, just sitting there and listening to people discussing the merits and demerits of MI was intriguing because 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!!!".

5. B. Stroustrup. The C++ Programming Language, 3rd ed. (Addison-Wesley, 1997).

Copyright © 2009 Herb Sutter