J
Joshua Cranmer ðŸ§
I think, the diamond problem w.r.t fields is over-rated.
Given your response below, I think you misunderstand the problem. Let me
try to explain it better. There are two separate issues: name
resolution, and class layout. Name resolution is relatively simple to
solve; it's the layout that is the thorny solution.
When you layout a class in memory, it is easiest to think of it as an
array of "slots" for actual field storage (think C structs if you're
used to that). Class layout is typically done by laying out the
superclass first and then using extra slots for the subclass. If done
this way, this lets you guarantee that all objects of class A, slot 0
always points to the same field, even if the object's dynamic type is
really a subclass of A.
Multiple inheritance throws a wrinkle into this scheme because of the
diamond problem. If D inherits from B and C, which both inherit from A,
then the standard way of laying out the class yields two copies of A. If
you cast D to type C, then a method of C that tries to access a field of
type A would access C's copy of A instead of B (and presumably D's
"primary" copy)'s copy. This is the first case I give. In this
situation, accessing a field only requires knowing the static type of
the object, not the dynamic type.
You can get around this by requiring knowledge about dynamic types. The
option taken by C++'s virtual bases is to have the class layout
mechanism create only one copy of A. In this case, there is no longer a
guarantee that the field is always at the same offset for the static
type of an object--you have to do a layer of indirection to find out
where A's slots are and then use those. This is case #3 (note that the
casting to use a superclass's fields is implicit here).
The final option is to refer to fields internally via a hashtable of
(qualified) name to their values. This is generally what is done in most
dynamic programming languages (Python, JS, etc.), which is why they can
suffer multiple inheritance: all property accesses already go through
the hashtable, so there is no extra cost. This is what I called case #2.
So, if some entity "is-a"nother entity, but "is-also-a" yet another
entity, then why is Java praised for forcing me to model an "is-also-a"
relation as a "has-a"?
There are certainly cases where multiple inheritance is warranted; I am
not disputing that. That said, there are also people who try to inherit
for the wrong reasons, so probably most cases where people think they
want to use multiple inheritance, they don't. (I'm speaking in
generalities, though, and all generalities are false).