154

I've always liked the idea of having multiple inheritance supported in a language. Most often though it's intentionally forgone, and the supposed "replacement" is interfaces. Interfaces simply do not cover all the same ground multiple inheritance does, and this restriction can occasionally lead to more boilerplate code.

The only basic reason I have ever heard for this is the diamond problem with base classes. I just can't accept that. To me, it comes off an awful lot like, "Well, it's possible to screw it up, so it's automatically a bad idea." You can screw up anything in a programming language though, and I mean anything. I just cannot take this seriously, at least not without a more thorough explanation.

Just being aware of this problem is 90% of the battle. Furthermore I think I heard something years ago about a general-purpose work-around involving an "envelope" algorithm or something like that (does this ring a bell, anyone?).

Concerning the diamond problem, the only potentially genuine problem I can think of is if you're trying to use a third-party library and can't see that two seemingly unrelated classes in that library have a common base class, but in addition to documentation, a simple language feature could, let's say, require that you specifically declare your intent to create a diamond before it'll actually compile one for you. With such a feature, any creation of a diamond is either intentional, reckless, or because one is unaware of this pitfall.

So that all being said...Is there any real reason most people hate multiple inheritance, or is it all just a bunch of hysteria that causes more harm than good? Is there something that I am not seeing here? Thank you.

Example

Car extends WheeledVehicle, KIASpectra extends Car and Electronic, KIASpectra contains Radio. Why doesn't KIASpectra contain Electronic?

  1. Because it is an Electronic. Inheritance vs. composition should always be an is-a relationship vs. a has-a relationship.

  2. Because it is an Electronic. There are wires, circuit boards, switches, etc. all up and down that thing.

  3. Because it is an Electronic. If your battery goes dead in the winter, you're in just as much trouble as if all your wheels suddenly went missing.

Why not use interfaces? Take #3, for instance. I don't want to write this over and over again, and I really don't want to create some bizarre proxy helper class to do this either:

private void runOrDont()
{
    if (this.battery)
    {
        if (this.battery.working && this.switchedOn)
        {
            this.run();
            return;
        }
    }
    this.dontRun();
}

(We're not getting into whether that implementation is good or bad.) You can imagine how there may be several of these functions associated with Electronic that are not related to anything in WheeledVehicle, and vice-versa.

I wasn't sure whether to settle down on that example or not, since there is room for interpretation there. You could also think in terms of Plane extending Vehicle and FlyingObject and Bird extending Animal and FlyingObject, or in terms of a much purer example.

23
  • 26
    it also encourages inheritance over composition... (when it should be the other way around) Commented Nov 14, 2013 at 16:02
  • 38
    "You can screw it up" is a perfectly valid reason to remove a feature. Modern language design is somewhat splintered on that point, but you can generally classify languages into "Power from restriction" and "Power from flexibility". Neither of which is "correct", I don't think, they both have strong points to make. MI is one of those things which is used for Evil more often than not, and thus restrictive languages remove it. Flexible ones don't, because "more often than not" isn't "literally always". That said, mixins/traits are a better solution for the general case, I think.
    – Phoshi
    Commented Nov 14, 2013 at 16:08
  • 10
    There are safer alternatives to multiple inheritance in several languages, that go beyond interfaces. Check out Scala's Traits - they act like interfaces with optional implementation, but have some restrictions that help prevent issues like the diamond problem from arising.
    – KChaloux
    Commented Nov 14, 2013 at 16:19
  • 5
    See also this formerly closed question: programmers.stackexchange.com/questions/122480/…
    – Doc Brown
    Commented Nov 14, 2013 at 17:09
  • 19
    KiaSpectra isn't an Electronic; it has Electronics, and may be an ElectronicCar (which would extend Car...)
    – Brian S
    Commented Nov 14, 2013 at 19:01

9 Answers 9

80

In many cases, people use inheritance to provide a trait to a class. For example think of a Pegasus. With multiple inheritance you might be tempted to say the Pegasus extends Horse and Bird because you've classified the Bird as an animal with wings.

However, Birds have other traits that Pegasi don't. For example, birds lay eggs, Pegasi have live birth. If inheritance is your only means of passing sharing traits then there's no way to exclude the egg laying trait from the Pegasus.

Some languages have opted to make traits an explicit construct within the language. Other's gently guide you in that direction by removing MI from the language. Either way, I can't think of a single case where I thought "Man I really need MI to do this properly".

Also let's discuss what inheritance REALLY is. When you inherit from a class, you take a dependency on that class, but also you have to support the contracts that class supports, both implicit and explicit.

Take the classic example of a square inheriting from a rectangle. The rectangle exposes a length and width property and also a getPerimeter and getArea method. The square would override length and width so that when one is set the other is set to match getPerimeter and getArea would work the same (2*length+2*width for perimeter and length*width for area).

There is a single test case that breaks if you substitute this implementation of a square for a rectangle.

var rectangle = new Square();
rectangle.length= 5;
rectangle.width= 6;
Assert.AreEqual(30, rectangle.GetArea()); 
//Square returns 36 because setting the width clobbers the length

It's tough enough to get things right with a single inheritance chain. It gets even worse when you add another to the mix.


The pitfalls I mentioned with the Pegasus in MI and the Rectangle/Square relationships are both the results of a inexperienced design for classes. Basically avoiding multiple inheritance is a way to help beginning developers avoid shooting themselves in the foot. Like all design principles, having discipline and training based on them allows you to in time discover when it's okay to break from them. See the Dreyfus Model of Skill Acquisition, at the Expert level, your intrinsic knowledge transcends reliance on maxims/principles. You can "feel" when a rule doesn't apply.

And I do agree that I somewhat cheated with a "real world" example of why MI is frowned upon.

Let's look at a UI framework. Specifically let's look at a few widgets that might at first brush look like they are simply a combination of two others. Like a ComboBox. A ComboBox is a TextBox that has a supporting DropDownList. I.e. I can type in a value, or I can select from a pre-ordained list of values. A naive approach would be to inherit the ComboBox from TextBox and DropDownList.

But your Textbox derives its value from what the user has typed. While the DDL gets its value from what the user selects. Who takes precedent? The DDL might have been designed to verify and reject any input that wasn't in its original list of values. Do we override that logic? That means we have to expose the internal logic for inheritors to override. Or worse, add logic to the base class that is only there in order to support a subclass (violating the Dependency Inversion Principle).

Avoiding MI helps you sidestep this pitfall altogether. And might lead to you extracting common, reusable traits of your UI widgets so that they can be applied as needed. An excellent example of this is the WPF Attached Property which allows a framework element in WPF to provide a property that another framework element can use without inheriting from the parent framework element.

For example a Grid is a layout panel in WPF and it has Column and Row attached properties that specify where a child element should be placed in the grid's arrangement. Without attached properties, if I want to arrange a Button within a Grid, the Button would have to derive from Grid so it could have access to the Column and Row properties.

Developers took this concept further and used attached properties as a way of componentizing behavior (for example here is my post on making a sortable GridView using attached properties written before WPF included a DataGrid). The approach has been recognized as a XAML Design Pattern called Attached Behaviors.

Hopefully this provided a little more insight on why Multiple Inheritance is typically frowned upon.

10
  • 15
    "Man I really need MI to do this properly" - see my answer for an exmaple. In a nutshell, orthogonal concepts that satisfy an is-a relationship make sense for MI. They are very rare, but they exist.
    – MrFox
    Commented Nov 14, 2013 at 19:38
  • 14
    Man, I hate that square rectangle example. There are plenty of more illuminating examples that don't require mutability.
    – asmeurer
    Commented Nov 15, 2013 at 0:09
  • 12
    I love that the answer to "provide a real example" was to discuss a fictional creature. Excellent irony +1
    – jjathman
    Commented Nov 15, 2013 at 2:04
  • 5
    So you are saying that multiple inheritance is bad, because inheritance is bad (you examples are all about single interface inheritance). Therefore, based on this answer alone, a language should either be rid of inheritance and interfaces, or have multiple inheritance. Commented Nov 15, 2013 at 9:50
  • 15
    I don't think these examples really work...no one says a pegasus is a bird and a horse...you say it's a WingedAnimal and a HoofedAnimal. The square and rectangle example makes a little more sense because you find yourself with an object that behaves differently than you would think based on its asserted definition. However, I think this situation would be the programmers fault for not catching this (if indeed this did prove to be a problem).
    – richard
    Commented Nov 16, 2013 at 5:01
62

Is there something that I am not seeing here?

Allowing multiple inheritence makes the rules about function overloads and virtual dispatch decidedly more tricky, as well as the language implementation around object layouts. These impact language designers/implementors quite a bit, and raise the already high bar to get a language done, stable and adopted.

Another common argument I've seen (and made at times) is that by having two+ base classes, your object almost invariably violates the Single Responsibility Principle. Either the two+ base classes are nice self-contained classes with their own responsibility (causing the violation) or they're partial/abstract types that work with each other to make a single cohesive responsibility.

In this other case, you have 3 scenarios:

  1. A knows nothing about B - Great, you could combine the classes because you were lucky.
  2. A knows about B - Why didn't A just inherit from B?
  3. A and B know about each other - Why didn't you just make one class? What benefit comes from making these things so coupled but partially replacable?

Personally, I think multiple inheritance has a bad rap, and that a well done system of trait style composition would be really powerful/useful... but there are a lot of ways that it can be implemented badly, and a lot of reasons it's not a good idea in a language like C++.

[edit] regarding your example, that's absurd. A Kia has electronics. It has an engine. Likewise, it's electronics have a power source, which just happens to be a car battery. Inheritance, let alone multiple inheritance has no place there.

4
  • 9
    Another interesting question is how do base classes + their base classes get initialized. Encapsulation > Inheritance. Commented Nov 14, 2013 at 16:41
  • "a well done system of trait style composition would be really powerful/useful..." - These are known as mixins, and they are powerful/useful. Mixins can be implemented using MI, but Multiple Inheritance is not required for mixins. Some languages support mixins inherently, without MI. Commented Nov 14, 2013 at 20:21
  • For Java in particular, we already have multiple interfaces and INVOKEINTERFACE, so MI wouldn't have any obvious performance implications. Commented Nov 15, 2013 at 5:10
  • Tetastyn, I agree that the example was bad and didn't serve his question. Inheritance isn't for adjectives (electronic), it's for nouns (vehicle).
    – richard
    Commented Nov 16, 2013 at 5:05
30

The only reason why it is disallowed is because it makes it easy for people to shoot themselves in the foot.

What usually follows in this sort of a discussion is arguments as to whether the flexibility of having the tools is more important than the safety of not shooting off your foot. There is no decidedly correct answer to that argument, because like most other things in programming, the answer depends on context.

If your developers are comfortable with MI, and MI makes sense in the context of what you are doing, then you will sorely miss it in a language that doesn't support it. At the same time if the team is not comfortable with it, or there is no real need for it and people use it 'just because they can', then that is counter-productive.

But no, there does not exist an all-convincing absolutely true argument that proves multiple inheritance to be a bad idea.

EDIT

Answers to this question appear to be unanimous. For the sake of being the devil's advocate I will provide a good example of multiple inheritance, where not doing it leads to hacks.

Suppose you are designing a capital markets application. You need a data model for securities. Some securities are equity products (stocks, real estate investment trusts, etc) others are debt (bonds, corporate bonds), others are derivatives (options, futures). So if you're avoiding MI, you will make a very clear, simple inheritance tree. A Stock will inherit Equity, Bond will inherit Debt. Great so far, but what about derivatives? They can be based off of Equity-like products or Debit-like products? Ok, I guess we will make our inheritance tree branch out more. Keep in mind, some derivatives are based off of equity products, debt products, or neither. So our inheritance tree is getting complicated. Then along comes the business analyst and tells you that now we support indexed securities (index options, index future options). And these things can be based off of Equity, Debt, or Derivative. This is getting messy! Does my index future option derive Equity->Stock->Option->Index? Why not Equity->Stock->Index->Option? What if one day I find both in my code (This happened; true story)?

The problem here is that these fundamental types can be mixed in any permutation that does not naturally derive one from the other. The objects are defined by an is a relationship, so composition makes no sense whatsoever. Multiple inheritance (or the similar concept of mixins) is the only logical representation here.

The real solution for this problem is to have the Equity, Debt, Derivative, Index types defined and mixed using multiple inheritance to create your data model. This will create objects that both, make sense, and lend easily to code re-use.

12
  • 30
    I've worked in finance. There's a reason why functional programming is preferred over O-O in financial software. And you've pointed it out. MI doesn't fix this problem it exacerbates it. Believe me I can't tell you how many times I've heard "It's just like this...except" when dealing with finance clients That except becomes the bane of my existence. Commented Nov 14, 2013 at 20:02
  • 9
    This seems very solvable with interfaces to me... in fact, it seems better suited to interfaces than anything else. Equity and Debt both implement ISecurity. Derivative has an ISecurity property. It may itself be an ISecurity if appropriate (I don't know finance). IndexedSecurities again contain an interface property which gets applied to the types it's allowed to be based off of. If they're all ISecurity, then they all have ISecurity properties and can be arbitrarily nested...
    – Bobson
    Commented Nov 14, 2013 at 20:30
  • 11
    @Bobson the problem comes in that you have to re-implement the interface for each implementor, and in many cases it IS just the same. You can use delegation/composition but then you lose access to private members. And since Java doesn't have delegates/lambdas...there is literally no good way to do it. Except possibly with an Aspect tool like Aspect4J Commented Nov 14, 2013 at 20:45
  • 10
    @Bobson exactly what Mike Brown said. Yes, you can design a solution with interfaces, but it will be clunky. Your intuition to use interfaces is very correct though, it is a hidden desire for mixins/multiple inheritance :).
    – MrFox
    Commented Nov 14, 2013 at 20:56
  • 13
    This touches on an oddity where OO programmers prefer to think in terms of inheritance and oft fail to accept delegation as a valid OO approach, one which typically yields a leaner, more maintainable and extensible solution. Having worked in finance and healthcare I've seen the unmanageable mess MI can create, especially as tax and health laws change year over year and a definition of an object LAST year is invalid for THIS year (yet must still perform the function of either year, and must be interchangeable). Composition yields leaner code that is easier to test, and it costs less over time.
    – wilson0x4d
    Commented Nov 15, 2013 at 9:53
11

The other answers here seem to be getting mostly into theory. So here's a concrete Python example, simplified down, that I've actually smashed headlong into, which required a fair amount of refactoring:

class Foo(object):
   def zeta(self):
      print "foozeta"

class Bar(object):
   def zeta(self):
      print "barzeta"

   def barstuff(self):
      print "barstuff"
      self.zeta()

class Bang(Foo, Bar):
   def stuff(self):
      self.zeta()
      print "---"
      self.barstuff()

z = Bang()
z.stuff()

Bar was written assuming it had its own implementation of zeta(), which is generally a pretty good assumption. A subclass should override it as appropriate so that it does the right thing. Unfortunately, the names were only coincidentally the same - they did rather different things, but Bar was now calling Foo's implementation:

foozeta
---
barstuff
foozeta

It is rather frustrating when there are no errors thrown, the application starts acting just ever so slightly wrong, and the code change that caused it (creating Bar.zeta) doesn't seem to be where the problem lies.

7
  • How is the Diamond problem avoided through calls to super()? Commented Nov 14, 2013 at 20:16
  • 6
    I'm glad C++'s MI is much better designed. The above bug is simply not possible. You have to manually disambiguate it before the code will even compile. Commented Nov 17, 2013 at 4:16
  • 2
    Changing the order of inheritance in Bang to Bar, Foo would also fix the problem - but your point is a good one, +1. Commented Nov 19, 2013 at 3:34
  • 2
    @Tyler: I still consider that "feature" a bug. Aka a "beature". Why would I want a compiler to automatically decide the right choice for me in the general case when manually disambiguating can never go wrong? Commented Aug 6, 2014 at 5:03
  • 2
    @ThomasEding You can manually disambiguate still: Bar.zeta(self). Commented Aug 7, 2014 at 21:43
10

I would argue that there aren't any real problems with MI in the right language. The key is to allow diamond structures, but require that subtypes provide their own override, instead of the compiler picking one of the implementations based on some rule.

I do this in Guava, a language I'm working on. One feature of Guava is that we can invoke a specific supertype's implementation of a method. So it's easy to indicate which supertype implementation should be "inherited", without any special syntax:

type Sequence[+A] {
  String toString() {
    return "[" + ... + "]";
  }
}

type Set[+A] {
  String toString() {
    return "{" + ... + "}";
  }
}

type OrderedSet[+A] extends Sequence[A], Set[A] {
  String toString() {
    // This is Guava's syntax for statically invoking instance methods
    return Set.toString(this);
  }
}

If we didn't give OrderedSet its own toString, we would get a compilation error. No surprises.

I find MI to be particularly useful with collections. For example, I like to use a RandomlyEnumerableSequence type to avoid declaring getEnumerator for arrays, deques, and so forth:

type Enumerable[+A] {
  Source[A] getEnumerator();
}

type Sequence[+A] extends Enumerable[A] {
  A get(Int index);
}

type RandomlyEnumerableSequence[+A] extends Sequence[A] {
  Source[A] getEnumerator() {
    ...
  }
}

type DynamicArray[A] extends MutableStack[A],
                             RandomlyEnumerableSequence[A] {
  // No need to define getEnumerator.
}

If we didn't have MI, we could write a RandomAccessEnumerator for several collections to use, but having to write a brief getEnumerator method still adds boilerplate.

Similarly, MI is useful for inheriting standard implementations of equals, hashCode and toString for collections.

11
  • 1
    @AndyzSmith, I see your point, but not all inheritance conflicts are actual semantic problems. Consider my example - none of the types make promises about toString's behavior, so overriding its behavior doesn't violate the substitution principle. You also sometimes get "conflicts" between methods which have the same behavior but different algorithms, especially with collections. Commented Nov 17, 2013 at 7:37
  • 1
    @Asmageddon agreed, my examples involve functionality only. What do you see as the advantages of mixins? At least in Scala, traits are essentially just abstract classes which permit MI - they can still be used for identity and still bring about the diamond problem. Are there other languages where mixins have more interesting differences? Commented Oct 5, 2014 at 21:30
  • 1
    Technically abstract classes can be used as interfaces, to me, the benefit simply the fact of the construct itself being an "usage tip", meaning I know what to expect when I see the code. That said, I am currently coding in Lua, which has no inherent OOP model - existing class systems commonly allow including tables as mixins, and the one I'm coding uses classes as mixins. The only difference is that you can only use (single)inheritance for identity, mixins for functionality(with no identity info), and interfaces for further checks. Maybe it is not somehow way better, but it makes sense to me. Commented Oct 6, 2014 at 20:01
  • 2
    Why do you call the Ordered extends Set and Sequence a Diamond? It is just a Join. It lacks the hat vertex. Why do you call it a diamond? I asked here but it seems a taboo question. How did you know that you need to call this triangualar structure a Diamond rather than Join?
    – Val
    Commented Jul 14, 2015 at 18:25
  • 2
    @RecognizeEvilasWaste that language has an implicit Top type which declares toString, so there was actually a diamond structure. But I think you have a point - a "join" without a diamond structure creates a similar problem, and most languages handle both cases the same way. Commented Jul 14, 2015 at 21:20
8

Inheritance, multiple or otherwise, is not that important. If two objects of different type are substitutable, that is what matters, even if they are not linked by inheritance.

A linked list and a character string have little in common, and need not be linked by inheritance, but it's useful if I can use a length function to get the number of elements in either one.

Inheritance is a trick to avoid repeated implementation of code. If inheritance saves you work, and multiple inheritance saves you even more work compared to single inheritance, then that's all the justification that is needed.

I suspect that some languages do not implement multiple inheritance very well, and to the practitioners of those languages, that is what multiple inheritance means. Mention multiple inheritance to a C++ programmer, and what comes to mind is something about issues when a class ends up with two copies of a base via two different inheritance paths, and whether to use virtual on a base class, and confusion about how destructors are called, and so on.

In many languages, inheritance of class is conflated with inheritance of symbols. When you derive a class D from a class B, not only are you creating a type relationship, but because these classes also serve as lexical namespaces, you are dealing with the importation of symbols from the B namespace to the D namespace, in addition to the semantics of what is happening with the types B and D themselves. Multiple inheritance therefore brings in issues of symbol clashing. If we inherit from card_deck and graphic, both of which "have" a draw method, what does it mean to draw the resulting object? An object system which doesn't have this problem is the one in Common Lisp. Perhaps not coincidentally, multiple inheritance gets used in Lisp programs.

Badly implemented, inconvenient anything (such as multiple inheritance) should be hated.

5
  • 2
    Your example with length() method of list and string containers is bad, since these two are doing completely different things. The purpose of inheritance is not to reduce the code repetition. If you want to reduce code repetition, you refactor common parts into a new class. Commented Nov 15, 2013 at 7:07
  • 2
    @BЈовић The two are not doing "completely" different things at all.
    – Kaz
    Commented Nov 15, 2013 at 7:09
  • 1
    Comparting string and list, one can see lots of repetition. Using inheritance to implement these common methods would simply be wrong. Commented Nov 15, 2013 at 7:17
  • 2
    @BЈовић It isn't said in the answer that a string and list should be linked by inheritance; quite the opposite. The behavior of being able to substitute a list or a string in a length operation is useful independently of the concept of inheritance (which in this case doesn't help: we are not likely to be able to achieve anything by trying to share the implementation between the two methods of the length function). There could nevertheless be some abstract inheritance: e.g. both list and string are of type sequence (but which doesn't provide any implementation).
    – Kaz
    Commented Nov 15, 2013 at 8:43
  • 2
    Also, inheritance is a way of refactoring parts to a common class: namely the base class.
    – Kaz
    Commented Aug 5, 2014 at 18:51
1

As far as I can tell, part of the problem (besides making your design a little bit harder to understand (nevertheless easier to code)) is that the compiler is going to save enough space for your class data, allowing this a huge amount of memory waste in the following case:

(My example might not be the best, but try to get the gist about multiple memory space for the same purpose, it was the first thing that came to my mind :P )

Concider a DDD wher the class dog extend from caninus and pet, a caninus has a variable which indicates the amount of food it should eat (an integer) under the name dietKg, but a pet also has another variable for that purpose usually under the same name (unless you set another variable name, then you'll have codify extra code, which was de initial problem that you wanted avoid, to handle and keep the integrity of bouth variables), then you'll have two memory spaces for the exact same purpose, to avoid this you will have to modify your compiler to recognize that name under the same namespace and just assign a single memory space to that data, which unfortunately is umposible to determine in compilation time.

You could, of course, design a languaje to specify that such variable might have already an space defined somewhere else, but at the end the programer should specify where is that memory space which this variable is referencing to (and again extra code).

Trust me the people implementing this thought really hard about all this, but I'm glad you asked, your kind prespective is the one which changes paradigms ;), and concider this, I'm not saying it is impossible (but many asumptions and a multiphased compiler must be implemented, and a really complex one), I'm just saying it does not exists yet, if you start a project for your own compiler capable of doing "this" (multiple inheritance) please let me know, I'll be glad to join to your team.

2
  • 1
    At what point could a compiler ever assume that variables of the same name from different parent classes are safe to combine? What guarantee do you have that caninus.diet and pet.diet actually serve the same purpose? What would you do with function/methods with the same name?
    – Mr.Mindor
    Commented Nov 14, 2013 at 23:03
  • hahaha I'm totally agree with you @Mr.Mindor that's exactly what I'm saying in my answer, the only way that comes to my mind to get near to that aproach is prototype orientated programming, but I'm not saying it's impossible, and that's exactly the reason why I said that that many asumptions must be made during compilation time, and some extra "data"/especifications must be written by teh programmer (nevertheless it is more coding, which was the original problem)
    – Ordiel
    Commented Nov 15, 2013 at 14:59
-1

For quite a while now it never really occurred to me how totally different some programming tasks are from others--and how much it helps if the languages and patterns used are tailored to the problem space.

When you are working alone or mostly isolated on code that you wrote it's a completely different problem space from inheriting a codebase from 40 people in India who worked on it for a year before handing it to you without any transitional help.

Imagine you were just hired by your dream company and then inherited such a code base. Furthermore imagine that the consultants had been learning about (and therefore infatuated with) inheritance and multiple-inheritance... Could you picture what you might be working on.

When you inherit code the most important feature is that it is understandable and the pieces are isolated so they can be worked on independently. Sure when you are first writing the code structures like multiple inheritance might save you a little duplication and seem to fit your logical mood at the time, but the next guy just has more stuff to untangle.

Every interconnection in your code also makes it more difficult to understand and modify pieces independently, doubly so with multiple inheritance.

When you are working as part of a team you want to target the simplest possible code that gives you absolutely no redundant logic (That's what DRY really means, not that you shouldn't type much just that you never have to change your code in 2 places to solve a problem!)

There are simpler ways to achieve DRY code than Multiple Inheritance, so including it in a language can only open you up to problems inserted by others who might not be at the same level of understanding as you are. It's only even tempting if your language is incapable of offering you a simple/less complex way to keep your code DRY.

2
  • Furthermore imagine that the consultants had been learning about - I've seen so many non-MI languages (eg .net) where the consultants went crazy with interface-based DI and IoC to make the whole thing an impenetrably convoluted mess. MI doesn't make this worse, probably makes it better.
    – gbjbaanb
    Commented Dec 3, 2015 at 11:23
  • @gbjbaanb You are right, it is easy for someone who hasn't been burnt to mess up any feature--in fact it's almost a requirement for learning a feature that you mess it up in order to see how to best use it. I'm kind of curious because you seem to be saying that not having MI forces more DI/IoC which I haven't seen--I wouldn't think that the consultant code you got with all the crappy interfaces/DI would have been better also having a whole crazy many-to-many inheritance tree, but it could be my inexperience with MI. Do you have a page I could read on replacing DI/IoC with MI?
    – Bill K
    Commented Dec 3, 2015 at 17:32
-3

The biggest argument against multiple inheritance is that some useful abilities can be provided, and some useful axioms can hold, in a framework which severely restricts it(*), but cannot cannot be provided and/or hold without such restrictions. Among them:

  • The ability to have multiple separately-compiled modules include classes which inherit from other module's classes, and recompile a module containing a base class without having to recompile every module that inherits from that base class.

  • The ability for a type to inherit a member implementation from a parent class without the derived type having to re-implement it

  • The axiom that any object instance may be upcast or downcast directly to itself or any of its base types, and such upcasts and downcasts, in any combination or sequence, are always identity-preserving

  • The axiom that if a derived class overrides and chains to a base-class member, the base member will be invoked directly by the chaining code, and will not be invoked anywhere else.

(*) Typically by requiring that types supporting multiple inheritance be declared as "interfaces" rather than classes, and not allowing interfaces to do everything normal classes can do.

If one wishes to allow generalized multiple inheritance, something else must give way. If X and Y both inherit from B, they both override the same member M and chain to the base implementation, and if D inherits from X and Y but does not override M, then given an instance q of type D, what should ((B)q).M() do? Disallowing such a cast would violate the axiom that says any object can be upcast to any base type, but any possible behavior of the cast and member invocation would violate the axiom regarding method chaining. One could require that classes only be loaded in combination with the particular version of a base class they were compiled against, but that is often awkward. Having the runtime refuse to load any type in which any ancestor can be reached by more than one route might be workable, but would greatly limit the usefulness of multiple inheritance. Allowing shared inheritance paths only when no conflicts exist would create situations where an old version of X would be compatible with an old or new Y, and an old Y would be compatible with an old or new X, but the new X and new Y would be compatible, even if neither did anything which in and of itself should be expected to be a breaking change.

Some languages and frameworks do allow multiple inheritance, on the theory that what's gained from MI is more important than what must be given up to allow it. The costs of MI are significant, however, and for many cases interfaces provide 90% of the benefits of MI at a small fraction of the cost.

12
  • Would down-voters claim to comment? I believe that my answer provides information not present in other languages, with regard to semantic advantages that can be received by disallowing multiple inheritance. The fact that allowing multiple inheritance would require giving up other useful things would be a good reason for anyone who values those other things more than multiple inheritance to oppose MI.
    – supercat
    Commented Aug 3, 2014 at 18:54
  • 2
    The problems with MI are easily solved or avoided altogether using the same techniques you'd use with single inheritance. None of the abilities/axioms you've mentioned is inherently hampered by MI except for the second one...and if you're inheriting from two classes that provide different behaviors for the same method, it's on you to resolve that ambiguity anyway. But if you find yourself having to do that, you're probably already misusing inheritance.
    – cHao
    Commented May 26, 2015 at 15:07
  • @cHao: If one requires that derived classes must override parent methods and not chain to them (same rule as interfaces) one can avoid the problems, but that's a pretty severe limitation. If one uses a pattern where FirstDerivedFoo's override of Bar does nothing but chain to a protected non-virtual FirstDerivedFoo_Bar, and SecondDerivedFoo's override chains to protected non-virtual SecondDerivedBar, and if code that wants to access base method uses those protected non-virtual methods, that might be a workable approach...
    – supercat
    Commented May 26, 2015 at 15:47
  • ...(eschewing the syntax base.VirtualMethod) but I don't know of any language support to particularly facilitate such an approach. I wish there were a clean way to attach one block of code to both a non-virtual protected method and a virtual method with the same signature without requiring a "one-liner" virtual method (which ends up taking a lot more than one line in source code).
    – supercat
    Commented May 26, 2015 at 15:52
  • There's no inherent requirement in MI that classes not call base methods, and no particular reason one would be needed. It seems a bit odd to blame MI, considering that problem also affects SI.
    – cHao
    Commented May 26, 2015 at 17:56

Not the answer you're looking for? Browse other questions tagged or ask your own question.