26

It's drilled into the newbie Java programmers that Java (pre-Java 8) has no multiple class inheritance, and only multiple interface inheritance, because otherwise you run into diamond inheritance problem (Class A inherits from classes B and C, both of which implement method X. So which of those classes' implementation is used when you do a.X() call?)

Clearly, C++ successfully addressed this, since it has multiple inheritance.

What's different between the internal design of Java and C++ - (i'm guessing in the method dispatch methodology, and vaguely recall that it had to do with vtables from my long-ago computer languages class) that allows this problem to be solved in C++ but prevents in Java?

To rephrase:

What is the methodology of the language design/implementation used in C++ to avoid the ambiguity of diamond inheritance problem, and why couldn't the same methodology be used in Java?

13
  • 60
    Clearly, C++ successfully addressed this, since it has multiple inheritance. Clearly C++ implemented multiple inheritance; that doesn't mean it addressed its problems. C++ has no problem with you shooting your feet off.
    – Doval
    Commented Dec 30, 2014 at 19:00
  • 5
    @Doval - unless calling a.X() in case of diamond inheritance is an undefined behavior, I would say that it solved the problem. Now, whether it solved ALL the conceptual problems of multiple inheritance as an idea, is a different story, but it's not germaine to what I'm asking.
    – DVK
    Commented Dec 30, 2014 at 19:03
  • 2
    @Snowman - sorry, we are still on 6 here. I'll update the question to restrict to 1-7, but feel free to post what Java 8 added that covered the gap and what wasn't available before.
    – DVK
    Commented Dec 30, 2014 at 19:13
  • 9
    Java also doesn't support integer types of explicit length (uint32_t, etc), preprocessing macros, raw pointers and explicitly allocated memory, in each case, a deliberate conscious decision about what the language should be. Just because a language doesn't implement something doesn't mean that the designers didn't know how.
    – user53141
    Commented Dec 30, 2014 at 22:33
  • 11
    In C++, programmers (are forced to) deal with the diamond inheritance problem. And "because they chose not to" isn't handwaving--it's how language design works: languages always include and exclude features at the discretion of their designer(s). Python, for instance, does not include increment/decrement operators; this has nothing to do with "internal design." Commented Dec 30, 2014 at 23:23

9 Answers 9

27

The fundamental difference is that Java defines conversions from a type to any of its supertype as being identity-preserving. This is a useful property to have, but it is incompatible with multiple inheritance. If d1 and d2 both derive from b, and thing is of a type which derives from both d1 and d2, then in the absence of severe restrictions on what d1 and d2 can do, it's possible that (b)(d1)thing and (b)(d2)thing should behave differently. If all casts from thing to b are required to go through d1 or d2, then it's possible for (b)(d1)thing and (b)(d2)thing to yield different objects, but that would imply that the casts are not identity-preserving.

Another difficulty which arises is that C++ is designed to be statically linked, which means that anything in the design of types d1 and d2 which would make use of common members ambiguous would be caught at link time. If the suppliers of d1 and d2 simultaneously add new features which are incompatible with each other, even though each would be compatible with the previous version of the other class, the person supplying the program that uses both would have to find a combination of classes that would work before the program could be linked, but consumers of the program would then be assured that the bundled versions of d1 and d2 would be compatible with each other.

In Java, it's expected that different parts of a program may be upgraded separately. If the author of a class releases a new version, code which uses that class can be made to use the new version without having to involve the original programmer of the consuming code. A consequence of this is that if certain combinations of versions of different classes will work, but a few combinations will generate troublesome ambiguities, it's likely that some such problems will only be discovered by a few unlucky end users who happen to have the exact troublesome combinations of versions.

While such versioning issues could arise in any case, multiple inheritance adds some kinds of failure which would be very hard to predict. If code is allowed to use a member which exists in one superclass but not the other without having to specify the superclass, then adding a like-named member to the other superclass would become a breaking change. Since the author of a class will generally have no idea with which other classes it may be combined, such an author would have no way of knowing what names were "safe" to use.

To be sure, some of the aforementioned problems can arise with Java's new "default interface implementations" feature; the value of the feature was judged sufficient to justify accepting them. Still, it's worthwhile to note that the dynamic linking of Java classes makes the tradeoffs very different from what they would be in a statically-linked language like C++.

14
  • 3
    The point about version-related incompatibilities is interesting; many MI-implementations would indeed have considerable problems there. However, your first paragraph about non-identity-preserving casts is only relevant for a specific implementation technique for inheritance (linear VTables). E.g. name-based dispatch mechanisms such as hash tables make casts unnecessary, but generally have worse performance.
    – amon
    Commented Dec 30, 2014 at 21:47
  • 4
    @amon: The problem isn't one of implementation, but semantics. Java guarantees that if b r1, r2, r3; r1=(b)(d1)thing; r2=(b)(d2)thing; r3=(b)thing;``, compiles, then r1, r2, and r3` will be equivalent. Unless members of b inherited through d1 are synonymous with those inherited from d2 (which would imply that neither d1 nor d2 could provide an override which was usable by a derived class), such an equivalence could not hold without violating some other common expectations of objects.
    – supercat
    Commented Dec 30, 2014 at 22:06
  • 1
    Isn't the fact that Java8 actually does have MI while still being identity preserving a contradiction with "This is a useful property to have, but it is incompatible with multiple inheritance"?
    – Voo
    Commented Dec 31, 2014 at 11:28
  • @supercat In more recent MI languages (such as Python and Scala), it is possible for d1 and d2 to both provide an override. Any class that inherits from both must specify the order of precedence. One consequence of this is that you can no longer assume that the method inherited from the superclass is the method inherited from b. In C++ this would be a violation of expectations, but in Python or Scala this would be expected, and is occasionally useful.
    – James_pic
    Commented Dec 31, 2014 at 12:26
  • @voo java 8 default method implementations aren't really multiple inheritance in the traditional way: the methods aren't really inherited from the interface, but are implemented in the class derived from it with an implementation copied out of the interface. This has a number of effects, most importantly that multiple methods with the same signature get merged into a single implementation, rather than having the possibility of ending up with multiple different implementations as you can with c++.
    – Jules
    Commented Dec 31, 2014 at 14:42
68

There's nothing fundamental in the internal design which causes this. The lack of multiple inheritance is a deliberate design decision in Java, not an external manifestation of a shortcoming in the internal design.

(I'm deliberately avoiding getting into the flame war as to whether multiple inheritance is a good idea or not).

8
  • 10
    Java does have vtables... (Or perhaps an equivalent design). Just think about it. Methods is virtual by default in that language. Commented Dec 30, 2014 at 19:16
  • 22
    One could even say that Java is vtables all the way down.
    – kdgregory
    Commented Dec 30, 2014 at 19:17
  • 16
    @DVK Nowhere are vtables mentioned in the C++ standard either. Commented Dec 30, 2014 at 21:36
  • 9
    Stroustrup designed the language knowing that vtables would be an efficient way to implement since the whole point of C++ was to keep the cost of OO light. But vtables are merely one way to solve the problem and whether Java uses them is completely irrelevant to this question. Java could easily have allowed multiple inheritance if the designers had desired, and indeed has interfaces, which are a sort of restricted multiple inheritance.
    – user53141
    Commented Dec 30, 2014 at 22:27
  • 12
    The general philosophy of Java is to make certain common errors impossible. The general philosophy of C++ is to allow the developer to do anything they want, even at the risk of creating certain common errors. Both languages succeed in implementing their philosophy.
    – user53141
    Commented Dec 31, 2014 at 18:38
41

Clearly, C++ successfully addressed this, since it has multiple inheritance.

Actually no, exactly the opposite. C++ did not successfully address this, and the diamond inheritance problem is a serious issue for C++ developers. There are techniques for dealing with it using "virtual inheritance", but often they make things more complicated.

Java and other languages that came after C++ didn't fail to implement multiple inheritance because they couldn't solve it the same way C++ did; they learned from C++'s mistakes and didn't implement a feature that introduces problems that are not easily solveable.

12
  • 8
    Actually, C++ gives you the choice between diamond and non-diamond inheritance, so this is not really the problem. Note that most languages which provide MI (eg. python, lisp) are very dynamic in nature, and allow you for instance to customize the way methods overrides are resolved. In C++, multiple inheritance is actually very crippled if you compare it to eg. lisp's CLOS with its multiple dispatch mechanism, and this reduces its usefulness to interface inheritance, à la Java. Commented Dec 30, 2014 at 22:19
  • 6
    @AlexandreC.: Yes, but then you've got dynamism to deal with. That's like dealing with a pain in your foot by amputating everything below the knee. Sure, the pain in your foot is now gone, but it's hardly what a reasonable person would consider "a good solution"! Commented Dec 30, 2014 at 22:34
  • 4
    "Serious problems". Good grief. The diamond is the best default behavior; if you think about it, it really is sensible unless you have poor design methodology to begin with. People taut about virtual inheritance, and IMO it is not needed much. Duplicating privates makes sense. Commented Dec 31, 2014 at 6:09
  • 5
    Saying multiple inheritance is a mistake in C++ shows a real lack of knowledge of how it is used in modern C++ design. Multiple inheritance makes the things you can do with templates dramatically more powerful. Commented Dec 31, 2014 at 10:33
  • 5
    @KevinMills How do you know the reason it never comes up isn't because people know it's a problem and therefore avoid it? You could just as easily say goto doesn't cause serious problems because it practically never comes up (or malloc/new, or any other feature or practice that general concensus says is problematic.) In practice learning C++ really means learning all the ways to avoid misusing its features.
    – Doval
    Commented Dec 31, 2014 at 14:15
16

The diamond inheritance problem is not solved in C++, in the sense that C++ does allow you to shoot yourself in the foot this way.

The problem is solved in Java because a deliberate design decision was made to disallow multiple inheritance of implementations, thereby omitting the design element from which the problem arises.

The real problem with multiple inheritance is that its complexity outweighs its potential benefit. That's principally why it was omitted from Java, not because of the diamond inheritance problem.

4
  • I distinctly remember my Languages CS class professor stating the contrary, though for the life of me I don't recall the reasoning since I haven't done C++... well, ever, since college.
    – DVK
    Commented Dec 30, 2014 at 19:01
  • 18
    The diamond "problem" is solved in C++ in the sense that one can (and thankfully must) disambiguate ambiguous member access. It also allows for virtual inheritance when needed. Commented Dec 30, 2014 at 19:14
  • 1
    Some languages use precedence rules to overcome the diamond problem. Namely Python, Scala. What are your thoughts on this approach ? Commented Dec 31, 2014 at 6:09
  • @AmoghTalpallikar: I'm not familiar with those languages, but the principle of having one primary base class while allowing other secondary classes would seem useful, especially if classes can independently allow or disallow primary and secondary inheritance.
    – supercat
    Commented Nov 11, 2016 at 14:13
15

To answer the OP's original question, there is a significant implementation feature in C++ not present in Java that allows multiple inheritance. The difference is that C++ must employ special tricks to fix up the 'this' pointer before entering a virtual function in a class with multiple concrete base classes. These tricks are vtable thunking and double-wide vtables.

C++ objects are arranged in memory as a contiguous block. Each derived class appends data to the block. Classes with a single inheritance chain start at one point in memory and all base classes and ancestors of the most derived class have the same 'this' pointer. Interfaces don't have any data so they don't affect the object layout. The 'this' pointer of all bases remains the same. This is true in C++, Java, C# and other languages that support objects with a single chain of concrete class inheritance along with any number of interfaces.

For a class with multiple concrete bases, only the layout of the first inheritance chain can be contiguous and coincident with the most derived class. The actual layout is implementation dependent and can become quite complex especially when there are several chains and several repeated copies of some base objects. In any case, the 'this' pointer changes depending on the point of view of each member function. This is not a problem for static or non-virtual functions, as the 'this' pointer is calculated at compile time. However, C++ must employ special tricks to fix up the 'this' pointer before entering a virtual function.

There were two popular strategies for this in the early days of C++. One was vtable thunking and the other was double-wide vtables. You can read more details about them in Inside the C++ Object Model by Stanley B Lippman. Essentially: 1) vtable thunking replaces the actual function address in the vtable with the address of a runtime thunk (an anonymous chunk of code) that adjusts the 'this' pointer then JMPs into the member function. The cost is a slight additional overhead for calling a virtual function in a class with multiple base classes. 2) double-wide vtables still store the member function address in the vtable but also the relevant 'this' pointer for that function (or a way to calculate it). The cost is that the vtable takes twice as much space for classes with multiple bases.

NOTE: Virtual Base Classes, including multiple virtual base classes, is an entirely different concept that uses an entirely different implementation. The common strategy for implementing VBCs is extending the vtable backward into negative offsets with pointers to the (not necessarily contiguous) data blocks of the virtual classes.

Of course, a class that has both virtual base classes and multiple concrete base classes will be quite a hairy monster indeed. I always marvel that C++ even compiles.

2
  • 1
    But there is no reason that Java couldn't have used such approaches. Commented Dec 31, 2014 at 10:28
  • 4
    It’s worth pointing out that what you describe here is a common implementation strategy, but there’s nothing that says C++ objects have to be designed this way. Also note that even for single inheritance, the vtable pointer (if one is used) will usually be changed during construction and destruction time to reflect which base object is currently existing. Commented Dec 31, 2014 at 10:44
11

Both the Java and C++ underlying systems are powerful enough to allow/support multiple inheritance.

Neither system provides any baked in solutions (or other assistance) to help developers deal with the consequences/potential problems.

In C++, the conscious decision was made to expose as much of the power of the underlying system to developers as possible, and allow them to decide which subsets of the system are worth using, and which are not. A good part of the decision, initially, probably had to do with the initial compiler implementations not having to do much extra work to support multiple inheritance. The decision stuck.

As far as Java goes, see here for an early (1995) explanation. The explicit decision was made to prevent multiple inheritance from being possible, at the language level - it was deemed to be too much of a "foot gun".

1

I might be misreading your question, but I'll give it a go, because I suspect it is a question I also had at one point.

It might look like Java doesn't have multiple implementation inheritance because it's hard to implement, while C++ has because its implementors found a way to implement it. This isn't the case. As others have said, it was a deliberate design decision to avoid a category of programming problems, not something to enable easier implementation of Java.

The very fact that Java has mulitple inheritance from interfaces means that its implementors have done the hard work of implementing multiple inheritance anyway - the fact that interfaces are abstract is mostly irrelevant.

In an implementation using vtables, to implement polymorphism under single inheritance, you need a vtable of the runtime type of your instance, perferably at a predetermined position. To implement polymorphism under multiple inheritance, you need multiple vtables.

For ease, let's say that each type's memory layout starts with its vtable (it's not required to be like this, but it'll make my description easier).

Type A [vtableA, mem1, mem2, ...]
Type B [vtableB, mem3, mem4, ...]
Type C : A, B [vtableA, mem1, mem2, ..., vtableB, mem3, mem4,...]
C* from C     ^

Under single inheritance, you just pass the object reference around and you can always find the vtable of its runtime type.

Type C : A, B [vtableA, mem1, mem2, ..., vtableB, mem3, mem4,...]
A* from C     ^    

Under multiple inheritance, in order to use substitution, you have to pass a reference such that, if interpretreted as a reference to an instance of a supertype, it will still work. This means that the reference should point to another vtable in the memory layout of the whole instance. This is simply done by adding an offset, until the new reference points to the new vtable. This offset is known at compile time, and it's probably the total size of the preceding data in the layout.

Type C : A, B [vtableA, mem1, mem2, ..., vtableB, mem3, mem4,...]
B* from C                                ^
              <----memory size of A---->

Notice that in the schematics I used "type", not "class". If you assume that the language doesn't allow you to declare your own data members, all the above still holds. The only difference is that Type B will now consist of only a vtable (because the language won't allow mem3, mem4, etc). But the mechanism is the same.

1

In C++ you can inherit from types that doesn't share common ancestor and avoid the "dreaded diamond" problem altogether. In fact this is what most of C++ code do: design around the problem. When there is no other option, then one can choose between virtual inheritance or non-virtual one. Each has its own advantages and disadvantages, but none is a truly "successfully addressed" solution.

In Java, every class inherits Object implicitly. Now this means that every multiple inheritance will create the "dreaded diamond" right away! Which means that even simple hierarchy would obligatory turn into a nightmare. This is where interface concept comes to the rescue. In C++ interface is just another class. In Java, interface is unique by not inheriting from Object. Java solves the "dreaded diamond" problem in same way that 99% of C++ code does it: by not letting diamonds to happen in the first place.

Bottom line: introducing diamond inheritance to Java would help in very few cases at a huge cost of allowing very difficult to understand code.

You must keep in mind that the biggest advantage that Java holds over C++ is that it doesn't let programmers to play with dangerous features that are mostly unnecessary (in jargon "shooting oneself in the foot"). Don't ask "why we don't have X", ask yourself "which types of problems cannot be solved without X". Most of the time you'd get a simple answer: "none". Or "none that matter to overwhelming majority of Java users".

0

As others have said I will question if C++ successfully addressed multiple inheritance.

(The only language I have seen that I consider successfully addressed multiple inheritance is Eiffel, but as we are not all using it, it is questionable if it was successfully. I expect that most programmers will find the way it does multiple inheritance too hard to learn as it takes more than 10 minutes thinking to understand it.)

However back to Java (and C#), in both cases it was a requirement that classes could be loaded at run time. And there is no known why to efficiently implement multiple inheritances if classes can be dynamically loaded.

I recall from the time when Java come out that this was the most common reason given by the people working on it in talks.

2
  • as far as I can tell, this seems to merely repeat points made and explained in this prior answer (there is even a reference to historical documentation of "time when Java come out" over there, not just vague recollection)
    – gnat
    Commented Dec 31, 2014 at 10:56
  • That answer talks about the risks of the “foot gun" with multiple inheritance, it was also a real issue that at the time that no one know a good way to implement it with run time loading of classes.
    – Ian
    Commented Dec 31, 2014 at 11:12

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