Multiple Inheritance

Whether you're talking about embedded software design, or C#/Java circles, it seems that Multiple Inheritance has acheived "Red-Headed Stepchild" status. For those readers who understand the English language, but aren't familiar with the ridiculous expressions English speakers use, being a red-headed stepchild isn't good, although I don't fully understand why. What I do understand are some of the prejudices against Multiple Inheritance, including unfamiliarity with the benefits it brings. This article attempts to justify Multiple Inheritance (MI) for a large number of software engineering domains.

The Need for Speed

First, let's discuss some of the common objections to using Multiple Inheritance. One of the common, and most ridiculous, objections made to MI is on the basis of performance. This objection is about as valid as claiming that the antenna ball on your car is a major factor in its poor gas mileage. While it is true that invoking methods based on virtual function table lookups is slower, in the vast majority of applications, this performance impact is very affordable. One reason for the over-concern over performance impacts is the advice of the C++ guru Scott Meyers. While Meyers' book is indeed a fantastic addition to any C++ developers' library, it is also based on ideas he formulated more than 15 years ago. In general, if you're writing code today the way you did 15 years ago, there's probably quite a few things that you're doing suboptimally. One timeless piece of advise Meyers does give in his book concerns performance optimization, and the applicability of the 80/20 rule. Most performance issues generally do arise from a small portion of poorly-designed code. It's much more likely that an inefficient bitmap manipulation algorithm, unnecessary data copying, or overzealous database interaction, are resposible for your applications' performance, rather than instances of multiple inheritance sprinkled throughout the code.

Multiple Inheritance generally tends to affect objects in your application which are greater in complexity, and lesser in popularity. By definition, you're going to be tempted to use MI for more complicated objects. Those objects generally (not always, but we're talking about the majority of cases here) will be instantiated in smaller quantities, and responsible for a smaller portion of the function calls in the system. If you have simple little objects that are used all over your software, it may make sense for you to worry about even the smallest performance side-effects. For example, if you are writing a mapping application, where you have a class called Coordinate, that represents points on the earth, and methods to manipulate them, this may be an object that gets an enormous amount of use. This kind of infrastructure class may have to be designed with "lean" considerations in mind. This factor is one reason why so much of the C++ standard library is written without MI. Maps and strings and lists are used all over the place in great numbers. Little inefficiencies in those objects can make a difference. However, some developers look at the C++ standard library as an indication of the way all code should be written. That's wrong. For one thing, if the C++ standard library was actually a good piece of code, it would be a lot more useful than it is. So, don't fancy yourself as an infrastructure developer, if in reality you're not (which is most of us!)

Duplication and Redundancy

Another common resistance to using Multiple Inheritance concerns the diabolical case of inheriting the same thing from multiple parents. For example, when both parents have a method of the same name, or have a member of the same name. This is similar to a little kid running into their parents' room fearing that there is a monster in the closet, because they read a story once where a little boy was eaten by such a closet creature. The simple response to this scenario should be to turn on the light, and look in the closet. Most of the time, you will find that in fact, there is no monster there, and even if there was, Hollywood has provided several practical solutions for dealing with these situations. In reality, the fear of this multiple inheritance issue is generally enough to prevent it in the first place. There also exist some easy ways to fix the problem.

In the case with inheriting the same method (signature) from multiple parents, you may have to help the compiler resolve the ambiguity

class Mother {
  public:
    void foo() {
      cout << "Mother::foo()" << endl;
    }
}

class Father {
  public:
    void foo() {
      cout << "Father::foo()" << endl;
    }
}

class ConfusedChild : public Mother, public Father {
  public:
    void foobar() {
      foo();             // ERROR: ambiguous reference to foo();
    }
}
Oh no! Honey, run up to the attic and grab the shotgun! Agghhh!

Ok, let's take a breath. First of all, nobody creates methods named foo(), except in articles like this one, so it's unlikely that you'll ever encounter a real method named foo. Next, there is a simple fix.

class ConfusedChild : public Mother, public Father {
  public:
    void foobar() {
      Father::foo();             // we really want the version of foo() from our Father
      bar();
    }
}
Wow, was that difficult? Not so much. What if we were scared away from using multiple inheritance, and choose to inherit only from Mother, and instead include a member of type Father to gain access to Father's methods.
class ConfusedChild : public Mother {
  public:
    void foobar() {
      m_father.foo();             // we really want the version of foo() from our Mother
      bar();
    }
  private:
    Father m_father;
}
In this case, we've added an extra member, and still have to type m_father. in front of foo(). So, this non-MI code is even more complex, verbose, and tightly-coupled.

Now, you might say that this isn't the tricky situation. Here, foo() is called by a subclass of Mother and Father, and the subclass is in an excellent position to know which inherited version of foo() should be called explicitly. True. It's a little more complicated if a user of ConfusedChild wants to call foo(). In fact, allowing other users of your class to treat it as an instance of your base class is a stronger justification for using inheritance (single, or multiple) than allowing your class to access the potential base class's methods with the minimal amount of typing. Here, we still have a reasonable solution.

class ConfusedChild : public Mother, public Father {
  public:
    virtual void foo() {
      Father::foo();
    }
}
Here, you resolve the foo() ambiguity for any of your users by providing an overridden version of foo() that explicitly chooses which inherited method should be called. Does this defeat some of the usefullness of MI? Yes. Does it make you write more code? Yes. Does it increase your code maintenance? Yes. But, no more so than picking a design that doesn't use MI at all. Basically, when you encounter (and you usually won't!) this ambiguity, you'll be reduced to something not much better than the non-MI solution. No worse. So, unless every single method in your base classes has such a conflict, you will get some benefit out of multiple inheritance. Which means that there really is no reason to avoid MI on the basis of this seldom-seen ambiguity, which the compiler will clearly warn you about anyway.

A related issue occurs when you inherit a member (maybe protected, maybe private) with the same name and type from multiple parents. While occuring infrequently, this can be a little trickier, but can be avoided and dealt with effectively. First of all, it generally is a wise decision to provide clear and descriptive method and member names in base classes that might be multiply-inherited from. If you name a method twistAndShout(), or a member m_RingoStarr, you're unlikely to encounter clashes with other classes. But, if you call your method run(), or your member m_ID, then you might have more trouble. So, that's the ounce of prevention that can be worth a pound of cure.

But, what it you can't help it, and you've inherited a member with the same name from two parents. This can occur if both parents inherit from the same "grandparent". Well then, you'll have two copies of that member, which is generally not what you want. Your reference of that member will have problems with ambiguity, just as our example with foo() above. If you want to gaurantee that your subclass only has one version of the (multiply) inherited member, you can use virtual inheritance. With virtual inheritance, you declare subclasses like so:

class Mother : public virtual Grandparent {}
class Father : public virtual Grandparent {}
class ConfusedChild : public Mother, public Father {}
This will keep you from acquiring two (non-synchronized) versions of a conflicting member. There is a cost to virtual inheritance. I won't delve into that here, but instead refer you to Scott Meyers' text. Nonetheless, this represents another scenario that:
  • is usually avoidable
  • can be handled, if encountered
  • if handled, does carry a performance impact that is unlikely to be noticed in most applications
Hardly the stuff of nightmares!

Conceptual Convolution

Another objection to Multiple Inheritance is the assertion that it's just too complex, or at least that it's more complex than single inheritance. Ok, the second part of that statement is true. But, is reference counting simpler? Are templates (a.k.a. generics) simpler? Are delegates (C#), or inner classes (Java), or functors and predicates, or lazy initialization simpler? No, no, no, no, no, and no (I may have missed a "no" somewhere). But, they are all useful tools that good developers need to have in their bag of tricks. While you may not want to employ these techniques in all your code, you certainly shouldn't be universally rejecting them solely on the basis of their complexity. Look under the hood of your car. Is what you see more or less complex than what your grandpa's pickup truck had under its hood? The complexity has been added to internal combustion engines iteratively, with specific goals in mind. We've now had more than a decade to get our arms around Multiple Inheritance. It's certainly not unreasonable to think that we should be prepared to understand its pitfalls, and still reap its benefits.

And the "too complex" argument is utter insanity. If you ask your average Joe on the street, who supposedly is less intelligent than your average developer, I can almost guarantee they understand Multiple Inheritance (although maybe not by that name). If Joe Schmoe's mother had the maiden name Schmuck, then Joe probably understands just fine that he has inherited certain properties and abilities from the Schmoes, and others from the Shmucks. He is a Schmoe, and he also is a Shmuck. His large ears came from the Schmoes, and his ability to run like the wind came from the Schmucks. Are you telling me that some Schmuck off the street can easily understand some concept that you can't? Personally, I am an Engineer, and I am an American. Both groups define me in certain ways. Sometimes in conflicting ways. But, I don't cry myself to sleep in fits of anguish because I can't come to grips with my true identity. Multiple inheritance really is the most natural of concepts, when you get right down to it.

C# and Java

Even the most stubborn developers these days generally embrace the watered-down variety of Multiple Inheritance used by C# and Java. In this variety, inheritance can be from an interface, which provides a gaurantee of a subclass's API, without actually providing an implementation. A class can use this type of "inheritance" as many times as it likes, as long as only one base class contains any actual implementation. This avoids the problem of inheriting the same thing from multiple base classes, that I showed wasn't really that difficult to deal with in the first place. Interfaces are lovely creatures, in that they do allow full polymorphism and consistency amongst implementations. However, they don't help much with respect to avoiding redundant code. If the actual behaviour really is common amongst many "implementations" of a given interface, then either:

  • The implementations will all have to aggregate (contain a member) in order to use a concrete class that provides the behaviour. Then, essentially, they will all have to code wrappers for all the methods from this member that the interface requires them to provide. This code will be of the form:
    interface Animal {
      public:
        abstract void eat();  // like C++ pure virtual
    }
    
    class GoodEater {
      void eat() {
        // TODO: remember to eat
      }
    }
    
    class Dog : Animal, BestFriendOfMan {
      public: 
        virtual void eat() {
           m_eater.eat();
        }
      private:
        GoodEater m_eater;   
    }
    
  • Worse yet, the implementations will all duplicate the code responsible for eating!
    class Dog : Animal, BestFriendOfMan {
      public: 
        virtual void eat() {
           // TODO: remember to eat
        }
    }
    class Cat : Animal {
      public: 
        virtual void eat() {
           // TODO: remember to eat, also
        }
    }
    
Now, I hope everyone is well aware of the dangers of over-eating! It would be vastly superior if we could delegate one class to do all the eating for us, and not even force us to be involved. Now that sounds like a healthier design. The first implementation of Dog above has the unnecessary declaration of eat(), the unnecessary aggregation of the GoodEater member, and the unnecessary implementation of eat() which uses the GoodEater. All this to save a virtual function table pointer? Do you really think the extra code you added in the first implementation runs without cost? Or that the duplicated code (ok, it's just a comment in my example .. but comments still give you carpal tunnel syndrome!) you added in the second implementation has no maintenance cost. What happens when someone finds a bug in the Dog's eat() method, and forgets to fix the same code in the Cat's eat() method? The cat starves, that's what. Don't make me pick up the phone and call People for the Ethical Treatment of Animals. Just use MI, and be done with it.

A common response to such an example is that if you find yourself with the scenario described above, it is a sign of a flawed design. For example, you could just make Dog inherit from BestFriendOfMan, and BestFriendOfMan inherit from Animal. Now, you only need single inheritance, you don't need to aggregate members, and you don't need to duplicate code. Great, if you can do it. But, it just may be that it's not possible to constrain BestFriendOfMan to be an Animal. It may be possible for Woman to be a BestFriendOfMan. So, it's just not true that only bad designs tempt you to use MultipleInheritance. Design your classes as they really are, logically. If you then need to use MI to help streamline code, and reduce redundancy, then do so.

So, we've shown that C# and Java are missing a feature that cannot always be efficiently duplicated using their available constructs. But, wait, aren't C# and Java newer, more advanced languages? In some ways, yes, and in others, no. Remember that Java was designed by a relatively small number of people. In its initial versions, it also didn't provide such useful tools as enums and generics. You might remember how many Java developers used to spend hours and hours trying to come up with a class-based implementation of enum types, that worked the way you expected them to. The fact is, Java hasn't always gotten it right. And with respect to MI, it still doesn't have it right.

After years of difficulty achieving the capabilities that MI can give you, Java architects invented Aspect-Oriented Programming. While AOP does have some universal benefits, to a large degree it was made necessary because true MI wasn't available in Java. Defining a Logging aspect, for example, is often used because it's just not possible to make a concrete Logger class that every other class can inherit implementation from. So, instead of using a complex feature that is built into the language and standardized (MI), Java developers now rely heavily on an even more complex feature that requires a new 3rd-party tool, which is not standard. This is progress? And let's all be honest here: the reason C# has the variey of MI that it does is because that's the way it is in Java. Don't fool yourself into thinking that since two of the newer languages don't support full Multiple Inheritance, that means it's not a valuable language feature.

Example

To emphasize what MI can be used for, and that it doesn't represent some perversion of design, I offer you the following example. Code can be downloaded here, or also browsed online. A handy VS2003 solution is even provided for your building pleasure.

In the example, we model a familiar system: online auctions. To have online auctions, we need an AuctionHouse to conduct the auctions, Buyers and Sellers. We also need Banks for the Buyers' and Sellers' accounts. A couple other classes are added to reduce redundancy and demonstrate code reuse thru Multiple Inheritance. In the example, a Buyer uses MI to inherit methods from AccountHolder, and AuctionBrowser (which indirectly provides inheritance from AuctionUser). Being an AccountHolder allows the Buyer to manage his bank account. It also allows the Bank to treat him/her as a normal AccountHolder. This is useful, for example, when the Bank wants to send out statements to all its AccountHolders. AccountHolder is a concrete class, not just an interface, because it provides the actual implementation of methods to receive monthly statements, deposit, and withdraw funds. In addition, the AuctionBrowser/AuctionUser is a concrete class, which gives Buyers the ability to login to view auctions, manage their username and password, and also receive emails from the AuctionHouse. This also allows the AuctionHouse to treat all users the same (as AuctionUsers) when it comes to sending out those lovely email advertisements (aka spam) to all its users, Buyers and Sellers alike.

The relationships of the classes are based off of real-world rules. We cannot force AuctionUser to inherit from AccountHolder simply to alleviate the need for Buyer to use Multiple Inheritance. This is because the AuctionHouse does not want to limit AuctionUsers to those with bank accounts. They lure in prospective buyers by allowing them to create accounts without verifying a bank account. That is an AuctionBrowser. This object can look at all the good things for sale to whet their appetite, but can't buy anything until becoming a full-fledged Buyer. If eBay® came to you offering millions to redesign their web service, would you tell them that they have to change their marketing policies because you don't like using Multiple Inheritance? I hope not!

As you can see, there is no convolution of design here. A Buyer clearly is an AuctionUser, and also a bank AccountHolder. Two unrelated concepts that both provide real code reuse to the Buyer. I challenge all readers to arrive at the same implementation with less code, and without duplication of interface or logic. Multiple Inheritance has saved us code, promoted robustness, reduced maintenance, and given me just cause to wag my finger and say "I told you so"! Isn't all that worth turning on the light and peeking into the closet?

If you made it all the way down to here, you are now ready to embrace Multiple Inheritance in your set of design skills ... and are probably also ready to fall asleep. With that, I bid you good night.

powered by Debian

Copyright ©2003-2010 Enscand, Inc.
All Rights Reserved

Modified February 25, 2010
Privacy
Security
Environment