132

I can see people asking all the time whether multiple inheritance should be included into the next version of C# or Java. C++ folks, who are fortunate enough to have this ability, say that this is like giving someone a rope to eventually hang themselves.

What’s the matter with multiple inheritance? Are there any concrete samples?

casperOne
  • 73,706
  • 19
  • 184
  • 253
Vlad Gudim
  • 23,397
  • 16
  • 69
  • 92
  • 60
    I would just mention that C++ is great for giving you enough rope to hang yourself. – tloach Oct 22 '08 at 14:26
  • 1
    For an alternative to multiple inheritance that addresses (and, IMHO solves) many of the same problems, look at Traits (http://www.iam.unibe.ch/~scg/Research/Traits/) – Bevan Oct 24 '08 at 09:12
  • 54
    I thought C++ gives you enough rope to shoot yourself in the foot. – KeithB Oct 25 '08 at 18:43
  • 7
    This question seems to assume that there is a problem with MI in general, whereas I've found lots of languages where MI is in casual use. There are certainly problems with certain languages' handling of MI, but I'm not aware that MI in general has significant problems. – David Thornley Jul 19 '10 at 15:08

11 Answers11

96

The most obvious problem is with function overriding.

Let's say have two classes A and B, both of which define a method doSomething. Now you define a third class C, which inherits from both A and B, but you don't override the doSomething method.

When the compiler seed this code...

C c = new C();
c.doSomething();

...which implementation of the method should it use? Without any further clarification, it's impossible for the compiler to resolve the ambiguity.

Besides overriding, the other big problem with multiple inheritance is the layout of the physical objects in memory.

Languages like C++ and Java and C# create a fixed address-based layout for each type of object. Something like this:

class A:
    at offset 0 ... "abc" ... 4 byte int field
    at offset 4 ... "xyz" ... 8 byte double field
    at offset 12 ... "speak" ... 4 byte function pointer

class B:
    at offset 0 ... "foo" ... 2 byte short field
    at offset 2 ... 2 bytes of alignment padding
    at offset 4 ... "bar" ... 4 byte array pointer
    at offset 8 ... "baz" ... 4 byte function pointer

When the compiler generates machine code (or bytecode), it uses those numeric offsets to access each method or field.

Multiple inheritance makes it very tricky.

If class C inherits from both A and B, the compiler has to decide whether to layout the data in AB order or in BA order.

But now imagine that you're calling methods on a B object. Is it really just a B? Or is it actually a C object being called polymorphically, through its B interface? Depending on the actual identity of the object, the physical layout will be different, and its impossible to know the offset of the function to invoke at the call-site.

The way to handle this kind of system is to ditch the fixed-layout approach, allowing each object to be queried for its layout before attempting to invoke the functions or access its fields.

So...long story short...it's a pain in the neck for compiler authors to support multiple inheritance. So when someone like Guido van Rossum designs python, or when Anders Hejlsberg designs c#, they know that supporting multiple inheritance is going to make the compiler implementations significantly more complex, and presumably they don't think the benefit is worth the cost.

Tarick Welling
  • 3,119
  • 3
  • 19
  • 44
benjismith
  • 16,559
  • 9
  • 57
  • 80
  • If you updated your answer with a description in layman terms of the obvious problem with overriding it would be my pleasure to select this answer as accepted. – Vlad Gudim Oct 22 '08 at 14:56
  • 29
    These aren't very convincing arguments - the fixed layout thing isn't tricky at all in most languages; in C++ it's tricky because memory isn't opaque and thus you may run into some difficulty with pointer arithmetic assumptions. In languages where class *definitions* are static (as in java, C# and C++), multiple inheritance name clashes can be forbidden compile time (and C# does this anyhow with interfaces!). – Eamon Nerbonne Sep 24 '09 at 18:04
  • 11
    The OP just wanted to understand the issues, and I explained them without personally editorializing on the matter. I just said that the language designers and compiler implementors "presumably don't think the benefit is worth the cost". – benjismith Sep 24 '09 at 23:23
  • 13
    "_The most obvious problem is with function overriding._" This has nothing to do with function overriding. It's a simple ambiguity problem. – curiousguy Nov 01 '11 at 02:42
  • 10
    This answer has some wrong info about Guido and Python, since Python supports MI. _"I decided that as long as I was going to support inheritance, I might as well support a simple-minded version of multiple inheritance."_ — Guido van Rossum http://python-history.blogspot.com/2009/02/adding-support-for-user-defined-classes.html — Plus, ambiguity resolution is fairly common in compilers (variables can be local to block, local to function, local to enclosing function, object members, class members, globals, etc.), I don't see how an extra scope would make a difference. – marcus Nov 29 '11 at 19:15
  • 2
    Are these really the main reasons for languages to not implement multiple inheritance? I mean: the ambiguity is quite easy to solve once one knows there's MI inside the tree.. And in managed environments I think memory layout shouldn't be a driving factor? – paul23 May 27 '12 at 22:40
  • 2
    I think the OP was asking about MI from the user (programmer) perspective, not the one of the compiler builder. – Peter - Reinstate Monica Jun 23 '14 at 23:05
  • @benjismith I do not see how the memory layout issue that you have mentioned only poses a problem in case of multiple inheritance. It is a issue with single inheritance also, right? – Geek Dec 16 '14 at 14:30
50

The problems you guys mention are not really that hard to solve. In fact e.g. Eiffel does that perfectly well! (and without introducing arbitrary choices or whatever)

E.g. if you inherit from A and B, both having method foo(), then of course you don't want an arbitrary choice in your class C inheriting from both A and B. You have to either redefine foo so it's clear what will be used if c.foo() is called or otherwise you have to rename one of the methods in C. (it could become bar())

Also I think that multiple inheritance is often quite useful. If you look at libraries of Eiffel you'll see that it's used all over the place and personally I've missed the feature when I had to go back to programming in Java.

  • 29
    I agree. The main reason why people hate MI is the same as with JavaScript or with static typing: most people have only ever used very bad implementations of it – or have used it very badly. Judging MI by C++ is like judging OOP by PHP or judging automobiles by Pintos. – Jörg W Mittag Dec 13 '08 at 12:40
  • 1
    Out of curiosity, what would you say are good languages by which to judge MI? – Devon_C_Miller Aug 07 '09 at 17:48
  • 1
    The Common Lisp Object System handles MI well (although it's rather idiosyncratic in its approach to OO). If you're into text adventures, now often known as interactive fiction, you'll find languages that rely heavily on MI at ifarchive.org. – David Thornley Jul 19 '10 at 15:10
  • 2
    @curiousguy: MI introduces yet another set of complications to worry about, just like many of the "features" of C++. Just because it is unambiguous does not make it easy to work with or debug. Removing this chain since it went off topic and you blew it off anyway. – Guvante Mar 29 '12 at 18:43
  • 1
    @Guvante The source of the "complication" here isn't MI, it's virtual inheritance. Indeed virtual inheritance has different semantic WRT non-virtual, like virtual vs. non-virtual functions: virtual functions offer weaker guaranties, but nobody is painting virtual functions as bad. Both virtual and non-virtual are useful, at different times. – curiousguy Mar 29 '12 at 19:53
  • 1
    C# already has an requirement for explicit inheritance implementation where the method signatures are the same. Why couldn't a similar approach apply to the class implementation? – nicodemus13 Nov 29 '12 at 17:22
  • 4
    @Guvante the only problem with MI in any language is shitty programmers thinking they can read a tutorial and suddenly know a language. – Miles Rout Jan 14 '13 at 07:38
  • 1
    @MilesRout: Any language feature needs to reduce coding time without significantly increasing bugs. Since encapsulation is quite powerful in any language, the value of MI isn't that high, however the number of bugs can be quite large when you consider any kind of interesting corner cases. I would agree that virtual inheritance is the culprit of bugs, but it is vastly more valuable than MI. – Guvante Jan 14 '13 at 15:54
  • 2
    I would argue that language features aren't just about reducing coding time. They're also about increasing the expressiveness of a language, and increasing performance. – Miles Rout Jan 16 '13 at 07:19
  • 4
    Also, bugs only occur from MI when idiots use it incorrectly. – Miles Rout Jan 16 '13 at 07:20
  • what do you mean rename a method if it is defined more than once? to what? sounds like a bad idea to me – Dominic Nov 20 '13 at 21:36
  • Having read this answer, I'm surprised not to see "The problems you guys mention are not really that hard to solve. In fact e.g. Eiffel does that perfectly well!" in an answer to basically every question on Stack Overflow. – David Moles Oct 26 '15 at 22:39
  • Well...if you miss Multiple Inheritance in Java, Inner Classes indirectly provide the support for it... – Manish Kumar Sharma Jan 24 '16 at 06:11
28

The diamond problem:

an ambiguity that arises when two classes B and C inherit from A, and class D inherits from both B and C. If there is a method in A that B and C have overridden, and D does not override it, then which version of the method does D inherit: that of B, or that of C?

...It is called the "diamond problem" because of the shape of the class inheritance diagram in this situation. In this case, class A is at the top, both B and C separately beneath it, and D joins the two together at the bottom to form a diamond shape...

gnat
  • 6,213
  • 108
  • 53
  • 73
J Francis
  • 1,358
  • 9
  • 15
  • 5
    which has a solution known as virtual inheritance. It's only a problem if you do it wrong. – Ian Goldby Dec 05 '13 at 16:13
  • 1
    @IanGoldby: Virtual inheritance is a mechanism for solving part of the problem, *if one does not need to allow identity-preserving upcasts and downcasts among all of the types from which an instance is derived or for which it is substitutable*. Given X:B; Y:B; and Z:X,Y; assume someZ is an instance of Z. With virtual inheritance, (B)(X)someZ and (B)(Y)someZ are distinct objects; given either, one could get the other via a downcast and upcast, but what if one has a `someZ` and wants to cast it to `Object` and then to `B`? Which `B` will it get? – supercat Dec 17 '13 at 23:35
  • 2
    @supercat Perhaps, but problems like that are largely theoretical, and in any case can be signalled by the compiler. The important thing is to be aware of what problem you are trying to solve, and then use the best tool, ignoring the dogma from people who would rather not concern themselves with understanding 'why?' – Ian Goldby Dec 18 '13 at 08:38
  • @IanGoldby: Problems like that can only be signaled by the compiler if it has simultaneous access to all the classes in question. In some frameworks, any change to a base class will always necessitate a recompile of all derived classes, but the ability to use newer versions of base classes without having to recompile derived classes (for which one might not have source code) is a useful feature for frameworks that can provide it. Further, the problems aren't just theoretical. Many classes in .NET rely upon the fact that a cast from any reference type to `Object` and back to that type... – supercat Dec 18 '13 at 16:55
  • ...will be identity-preserving. If one didn't care about being able to use newer versions of base classes without having to recompile all derived classes, and or the ability to use identity-preserving upcasts and downcasts, then virtual inheritance might represent a usable solution to the Diamond Problem. On the other hand, the makers of .NET and Java thought the aforementioned abilities were more useful than MI would be; I see no way to add generalized MI to a framework without giving up one or both of those abilities. – supercat Dec 18 '13 at 16:59
  • @supercat I totally accept your point in the context of the original question. However, I was replying to a comment that seemed to imply that the only way to avoid the diamond problem is to avoid MI altogether. The truth is much more nuanced, and involves design tradeoffs as you've so well explained. – Ian Goldby Dec 18 '13 at 20:30
  • 3
    @IanGoldby: Fair enough. My point was that the implementers of Java and .NET weren't just "lazy" in deciding not to support generalized MI; supporting generalized MI would have prevented their framework from upholding various axioms whose validity is more useful to many users than MI would be. – supercat Dec 18 '13 at 21:24
  • You could simply introduce a rule for the function overriding problem, like use the function from the left one – Ini Jan 08 '19 at 07:05
21

Multiple inheritance is one of those things that is not used often, and can be misused, but is sometimes needed.

I never understood not adding a feature, just because it might be misused, when there are no good alternatives. Interfaces are not an alternative to multiple inheritance. For one, they don't let you enforce preconditions or postconditions. Just like any other tool, you need to know when it is appropriate to use, and how to use it.

KeithB
  • 16,577
  • 3
  • 41
  • 45
  • Can you explain why they don't let you enforce pre and post conditions? – Yttrill Jan 02 '11 at 16:45
  • 2
    @Yttrill because interfaces cannot have method implementations. Where do you put the `assert`? – curiousguy Nov 01 '11 at 02:50
  • 1
    @curiousguy: you use a language with a suitable syntax which allows you to put the pre- and post- conditions directly into the interface: no "assert" needed. Example from Felix: fun div(num: int, den: int when den != 0) : int expect result == 0 implies num == 0; – Yttrill Dec 11 '11 at 21:25
  • @Yttrill OK, but some languages, like Java, do not support either MI or "pre- and post- conditions directly into the interface". – curiousguy Dec 11 '11 at 21:37
  • It's not used often becouse it's not available, and we don't know how to use it well. If you take a look at some Scala code, you'll see how things start to be common and can be refactored to traits (Ok, it's not MI, but proves my point). – santiagobasulto Apr 02 '12 at 11:03
  • Traits in Scala bear some of the same problems as MI as they are stateful. So the order in which a class is extended by traits also matters, see http://blog.jetbrains.com/kotlin/2011/08/multiple-inheritance-part-2-possible-directions/ – OlliP Feb 26 '13 at 13:37
  • I would disagree on the whole "not used often" thing; one very popular web framework is Django, and if you are using one of many different Django plugins, there is a high chance you are using multiple inheritance, take django-braces for example. – semicolon Feb 17 '15 at 08:15
17

let's say you have objects A and B which are both inherited by C. A and B both implement foo() and C does not. I call C.foo(). Which implementation gets chosen? There are other issues, but this type of thing is a big one.

tloach
  • 8,009
  • 1
  • 33
  • 44
  • 2
    But that's not really a concrete example. If both A and B have a function, it's very likely that C will need it's own implementation as well. Otherwise it can still call A::foo() in it's own foo() function. – Peter Kühne Oct 22 '08 at 14:25
  • @Quantum: What if it doesn't though? It's easy to see the problem with one level of inheritance, but if you have lots of levels and you have some random function that is somewhere twice this becomes a very hard problem. – tloach Oct 22 '08 at 14:27
  • Also, the point isn't that you can't call the method A or B by specifying which one you want, the point is that if you don't specify then there's no good way to choose one. I'm not certain how C++ handles this, but if someone knows could the mention it? – tloach Oct 22 '08 at 14:29
  • 2
    @tloach - if C does not resolve the ambiguity, the compiler can detect this error and return a compile-time error. – Eamon Nerbonne Sep 24 '09 at 18:12
  • @Earmon - Due to polymorphism, if foo() is virtual the compiler might not even know at compile-time that this is going to be an issue. – tloach Aug 26 '10 at 19:01
  • "_the compiler might not even know at compile-time that this is going to be an issue_" Can you give an example? – curiousguy Nov 01 '11 at 02:52
  • Hi, could someone tell me why does this situation as described in this answer NOT create a compile time error? – ShayanK Mar 21 '13 at 14:01
  • @curiousguy: In many frameworks including both Java and .net, a project will often contain many separately-compiled units which are not combined until code is loaded for execution; one design goal of such frameworks is to allow a class to be recompiled without requiring the recompilation of all classes that either use it or derive from it. As such, if `Foo` inherits from `Moo` and `Goo`, both of which override `Boo`, and neither `Moo` nor `Goo` overrides `Boo`'s virtual function `Fred`, then there's no ambiguity accessing member `Fred` of a `Foo`. On the other hand, ... – supercat Apr 04 '13 at 22:11
  • ...even if the versions of `Moo` and `Goo` that existed when `Fred` was compiled don't override `Fred`, it's possible that later versions of `Moo` and `Goo` might do so. Such conflicts could not be detected until load time; worse, an old `Foo` might be usable in combination with a new `Moo` and an old `Goo`, or an old `Moo` and new `Goo`, but not with a new `Moo` and a new `Goo`. Some compatibility problems are unavoidable, but MI makes things worse than they would be without it. – supercat Apr 04 '13 at 22:15
  • @ShayanAli If you try to invoke a method, but which method is designated is ambiguous, there will be compile time error. – curiousguy Apr 06 '13 at 17:33
  • @supercat It is not clear what you are talking about. Can you submit complete minimal code showing the problem? – curiousguy Apr 06 '13 at 21:49
  • @curiousguy: `class Boo {public virtual void Fred() {...} ; public virtual void Barney() {...} class Moo:Boo {...} class Goo:Boo {...} class Foo : Moo,Goo { override void Barney() { Fred(); } }`. Assume all classes are in separate assemblies. What should calling `Barney` on a `Foo` do? Now suppose a new version of `Moo` adds a `Fred` override, and `Goo` does as well. Now what should be the effect of `Boo it = new Foo(); it.Barney();`? – supercat Dec 17 '13 at 23:41
  • @supercat I see. Let's write in C++: `class Moo: public virtual Boo {...} class Goo: public virtual Boo {...}`. When using virtual inheritance, **overriding is not an implementation detail**. You cannot add an overrider of virtual base virtual function, because you have an **overriding contract**. virtual and non-virtual inheritance are different, and there isn't one good and one bad inheritance, they have different properties. If you think overriding should be an implementation detail than just use non-virtual inheritance. Of course you will not be able to write `Boo it = new Foo();` then. – curiousguy Dec 20 '13 at 05:39
  • "_Such conflicts could not be detected until load time_" because you are using a broken programming language. In a non-broken language, overriders are declared in the interface files, and C++ is non-broken in this respect. So there isn't a fundamental issue here. – curiousguy Dec 20 '13 at 05:42
  • @curiousguy: I would posit that in general, it's good for a framework to allow newer versions of a class to extend the list of types for which it is substitutable while retaining binary compatibility with older versions. Replacing interfaces with MI would degrade that ability. On the other hand, perhaps a framework could allow MI and include interfaces, with the rule being that for a class to extend the list of *classes* from which it inherits would break compatibility, but extending the list of *interfaces* it implements would not; further, require that diamonds would be allowed... – supercat Dec 20 '13 at 19:01
  • ...only if both/all intermediate classes have consistent specifications with regard to all base-class members [e.g. all could promise not to override members of the base class, or all but one could promise to accept sibling overrides]. Changing such specifications for a class would break binary compatibility. In any case, even if generalized MI wouldn't make it impossible to design a framework to allow binary-compatible upgrades to classes, it would certainly make it harder to design a framework which is flexible and robust in that regard. – supercat Dec 20 '13 at 19:08
  • @supercat Disregarding any binary compatibility issue, programming with multiple virtual inheritance requires discipline; C++ books describe adequate patterns that do not lead to ambiguity; when proper discipline is applied, every virtual function has a final overrider. Programmers need to learn proper use of MI. Many arguments against MI are from people who never learnt these coding practices, and who naively believe they can throw up any set of classes with a common base and no clear specification of derived classes roles. – curiousguy Dec 20 '13 at 23:00
  • ... This coding discipline results in valid C++ programs, but the discipline is outside the scope of the language: the roles of derived classes are not described by the type system. This doesn't mean that specific idioms can't be formally described and checked by style checker. Even without this sort of meta type checking tool, programmers use good old test suites, including compile time test suites, to check their changes. The problem you mentioned (adding an override which breaks derived classes) would be detected by these tests. – curiousguy Dec 20 '13 at 23:07
  • @curiousguy: It may be possible for a validator to determine whether changes to an intermediate level class have been restricted to kinds which are unlikely to break binary compatibility with any possible derived class, though unless the framework is designed carefully the kinds of changes that won't risk such breakage would be rather limited. It won't in general be possible to test whether changes to a class will affect any of the classes that *actually* derive from it, however, because in many cases the author of the intermediate class won't have access to all derivatives. – supercat Dec 20 '13 at 23:20
  • ... These constraints could also be integrated in some evolved programming language as a meta type system. This would mandate error diagnostic by the compiler (no external tool required) as soon as an excess overrider is declared, possibly with a clearer message than with test cases. – curiousguy Dec 20 '13 at 23:21
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/43633/discussion-between-curiousguy-and-supercat) – curiousguy Dec 20 '13 at 23:23
6

I don't think the diamond problem is a problem, I would consider that sophistry, nothing else.

The worst problem, from my point of view, with multiple inheritance is RAD - victims and people who claim to be developers but in reality are stuck with half - knowledge (at best).

Personally, I would be very happy if I could finally do something in Windows Forms like this (it's not correct code, but it should give you the idea):

public sealed class CustomerEditView : Form, MVCView<Customer>

This is the main issue I have with having no multiple inheritance. You CAN do something similar with interfaces, but there is what I call "s*** code", it's this painful repetitive c*** you have to write in each of your classes to get a data context, for example.

In my opinion, there should be absolutely no need, not the slightest, for ANY repetition of code in a modern language.

Turing Complete
  • 929
  • 2
  • 12
  • 19
  • I tend to agree, but only tend: there is a need from some redundancy in any language to detect mistakes. Anyhow you should join the Felix developer team because that's a core goal. For example, all declarations are mutually recursive, and you can see forward as well as backwards so you don't need forward declarations (scope is set-wise, like C goto labels). – Yttrill Jan 02 '11 at 16:51
  • I completely agree with this - I just ran into a similar problem [here](http://stackoverflow.com/questions/5670917/how-to-avoid-reusing-identical-implementation-code-with-an-interface). People talk about the diamond problem, they quote it religiously, but in my opinion it's so *easily* avoided. (We don't all need to write our programs like they wrote the iostream library.) Multiple inheritance should logically be used when you have an object that needs the functionality of two differing base classes that have no overlapping functions or function names. In the right hands, it's a tool. –  Apr 15 '11 at 00:43
  • 3
    @Turing Complete: wrt not having any code repetition: this is a nice idea but it is incorrect and impossible. There are a huge number of usage patterns and we desire to abstract common ones into the library, but it is lunacy to abstract all of them because even if we could the semantic load remembering all the names is too high. What you want is a nice balance. Don't forget repetition is what gives things structure (pattern implies redundancy). – Yttrill Dec 24 '11 at 05:22
  • @lunchmeat317: The fact that code generally shouldn't be written in such a way that the 'diamond' would pose a problem, doesn't mean that a language/framework designer can just ignore the issue. If a framework provides that upcasting and downcasting preserve object identity, wishes to allow for later versions of a class to increase the number of types for which it can be substituted without that being a breaking change, and wishes to allow run-time type creation, I don't think it could allow multiple class inheritance (as opposed to interface inheritance) while meeting the above goals. – supercat Aug 30 '12 at 18:36
5

The main problem with multiple inheritance is nicely summed up with tloach's example. When inheriting from multiple base classes that implement the same function or field it's the compiler has to make a decision about what implementation to inherit.

This get's worse when you inherit from multiple classes that inherit from the same base class. (diamond inheritance, if you draw the inheritance tree you get a diamond shape)

These problems are not really problematic for a compiler to overcome. But the choice the compiler has to make here are rather arbitrary, this make code far less intuitive.

I find that when doing good OO design I never need multiple inheritance. In cases I do need it I usually find I've been using inheritance to reuse functionality while inheritance is only appropriate for "is-a" relations.

There are other techniques like mixins that solve the same problems and don't have the issues that multiple inheritance has.

Mendelt
  • 36,795
  • 6
  • 74
  • 97
  • 4
    The compiled *doesn't* need to make an arbitrary choice - it can simply error out. In C#, what's the type of `([..bool..]? "test": 1)`? – Eamon Nerbonne Sep 24 '09 at 18:09
  • 5
    In C++, the compiler never makes such arbitrary choices: it's an error to define a class where the compiler would need to make an arbitrary choice. – curiousguy Dec 11 '11 at 21:38
3

There is nothing wrong in multiple inheritance itself. The problem is to add multiple inheritance to a language that was not designed with multiple inheritance in mind from the start.

The Eiffel language is supporting multiple inheritance without restrictions in a very efficient and productive way but the language was designed from that start to support it.

This feature is complex to implement for compiler developers, but it seems that that drawback could be compensated by the fact that a good multiple inheritance support could avoid the support of other features (i.e. no need for Interface or Extension Method).

I think that supporting multiple inheritance or not is more a matter of choice, a matter of priorities. A more complex feature takes more time to be correctly implemented and operational and may be more controversial. The C++ implementation may be the reason why multiple inheritance was not implemented in C# and Java...

Christian Lemer
  • 893
  • 9
  • 15
  • 1
    C++ support for MI is not "_very efficient and productive_"? – curiousguy Nov 01 '11 at 02:44
  • 1
    Actually it's somewhat broken in the sense it doesn't fit in with other features of C++. Assignment doesn't work properly with inheritance, let alone multiple inheritance (check out the really bad rules). Creating diamonds correctly is so hard the Standards committee screwed up the exception hierarchy to keep it simple and efficient, rather than doing it correctly. On an older compiler I was using at the time I tested this and a few MI mixins and implementations of basic exceptions cost over a Megabyte of code and took 10 minutes to compile .. just the definitions. – Yttrill Dec 14 '11 at 09:52
  • 1
    Diamonds is a good example. In Eiffel, the diamond is resolved explicitly. For example, imagine Student and Teacher both inheriting from Person. The Person has a calendar, so both Student and Teacher will inherit this calendar. If you build a diamond by creating a TeachingStudent that inherits from both Teacher and Student, you may decide to rename one of the inherited calendar to keep both calendars available separately or decide to merge them so that it behaves more like Person. Multiple inheritance can be implemented nicely, but it requires a careful design an preferably from the start... – Christian Lemer Feb 06 '12 at 19:24
  • 1
    Eiffel compilers have to do a global program analysis to implement this model of MI efficiently. For polymorphic method calls they use either dispatcher thunks or sparse matrices as explained [here](http://www.faqs.org/faqs/eiffel-faq/). This doesn't mix well with C++'s separate compilation and C#'s and Java's class loading feature. – cyco130 Nov 12 '12 at 07:28
3

The Common Lisp Object System (CLOS) is another example of something that supports MI while avoiding the C++-style problems: inheritance is given a sensible default, while still allowing you the freedom to explicitly decide how exactly to, say, call a super's behaviour.

Frank Shearar
  • 17,012
  • 8
  • 67
  • 94
  • Yes, CLOS is one of the most superior object systems since the inception of modern computing in maybe even long since past :) – rostamn739 Jun 12 '16 at 11:17
3

The diamond is not a problem, as long as you don’t use anything like C++ virtual inheritance: in normal inheritance each base class resembles a member field (actually they are laid out in RAM this way), giving you some syntactic sugar and an extra ability to override more virtual methods. That may impose some ambiguity at compile-time but that’s usually easy to solve.

On the other hand, with the virtual inheritance it too easily goes out of control (and then becomes a mess). Consider as an example a “heart” diagram:

  A       A
 / \     / \
B   C   D   E
 \ /     \ /
  F       G
    \   /
      H

In C++ it is entirely impossible: as soon as F and G are merged into a single class, their As are merged too, period. That means you may never consider base classes opaque in C++ (in this example you have to construct A in H so you have to know that it present somewhere in the hierarchy). In other languages it may work, however; for example, F and G could explicitly declare A as “internal,” thus forbidding consequent merging and effectively making themselves solid.

Another interesting example (not C++-specific):

  A
 / \
B   B
|   |
C   D
 \ /
  E

Here, only B uses virtual inheritance. So E contains two Bs that share the same A. This way, you can get an A* pointer that points to E, but you can’t cast it to a B* pointer although the object is actually B as such cast is ambiguous, and this ambiguity can’t be detected at compile time (unless the compiler sees the whole program). Here is the test code:

struct A { virtual ~A() {} /* so that the class is polymorphic */ };
struct B: virtual A {};
struct C: B {};
struct D: B {};
struct E: C, D {};

int main() {
        E data;
        E *e = &data;
        A *a = dynamic_cast<A *>(e); // works, A is unambiguous
//      B *b = dynamic_cast<B *>(e); // doesn't compile
        B *b = dynamic_cast<B *>(a); // NULL: B is ambiguous
        std::cout << "E: " << e << std::endl;
        std::cout << "A: " << a << std::endl;
        std::cout << "B: " << b << std::endl;
// the next casts work
        std::cout << "A::C::B: " << dynamic_cast<B *>(dynamic_cast<C *>(e)) << std::endl;
        std::cout << "A::D::B: " << dynamic_cast<B *>(dynamic_cast<D *>(e)) << std::endl;
        std::cout << "A=>C=>B: " << dynamic_cast<B *>(dynamic_cast<C *>(a)) << std::endl;
        std::cout << "A=>D=>B: " << dynamic_cast<B *>(dynamic_cast<D *>(a)) << std::endl;
        return 0;
}

Moreover, the implementation may be very complex (depends on language; see benjismith’s answer).

  • That's the real problem with MI. Programmers may need different resolutions within one class. A language-wide solution would limit what is possible and force programmers to create kludges to get the program to work correctly. – shawnhcorey Jan 27 '18 at 14:55
2

One of the design goals of frameworks like Java and .NET is to make it possible for code which is compiled to work with one version of a pre-compiled library, to work equally well with subsequent versions of that library, even if those subsequent versions add new features. While the normal paradigm in languages like C or C++ is to distribute statically-linked executables that contain all of the libraries they need, the paradigm in .NET and Java is to distribute applications as collections of components that are "linked" at run-time.

The COM model which preceded .NET attempted to use this general approach, but it didn't really have inheritance--instead, each class definition effectively defined both a class and an interface of the same name which contained all its public members. Instances were of the class type, while references were of the interface type. Declared a class as deriving from another was equivalent to declaring a class as implementing the other's interface, and required the new class to re-implement all public members of the classes from which one derived. If Y and Z derive from X, and then W derives from Y and Z, it won't matter if Y and Z implement X's members differently, because Z won't be able to use their implementations--it will have to define its own. W might encapsulate instances of Y and/or Z, and chain its implementations of X's methods through theirs, but there would be no ambiguity as to what X's methods should do--they'd do whatever Z's code explicitly directed them to do.

The difficulty in Java and .NET is that code is allowed to inherit members and have accesses to them implicitly refer to the parent members. Suppose one had classes W-Z related as above:

class X { public virtual void Foo() { Console.WriteLine("XFoo"); }
class Y : X {};
class Z : X {};
class W : Y, Z  // Not actually permitted in C#
{
  public static void Test()
  {
    var it = new W();
    it.Foo();
  }
}

It would seem like W.Test() should creating an instance of W call the implementation of virtual method Foo defined in X. Suppose, however, that Y and Z were actually in a separately-compiled module, and although they were defined as above when X and W were compiled, they were later changed and recompiled:

class Y : X { public override void Foo() { Console.WriteLine("YFoo"); }
class Z : X { public override void Foo() { Console.WriteLine("ZFoo"); }

Now what should be the effect of calling W.Test()? If the program had to be statically linked before distribution, the static link stage might be able to discern that while the program had no ambiguity before Y and Z were changed, the changes to Y and Z have made things ambiguous and the linker could refuse to build the program unless or until such ambiguity is resolved. On the other hand, it's possible that the person who has both W and the new versions of Y and Z is someone who simply wants to run the program and has no source code for any of it. When W.Test() runs, it would no longer be clear what W.Test() should do, but until the user tried to run W with the new version of Y and Z there would be no way any part of the system could recognize there was a problem (unless W was considered illegitimate even before the changes to Y and Z).

supercat
  • 77,689
  • 9
  • 166
  • 211