More On Multiple Inheritance

Matthew Tuck matty@box.net.au
Sat, 27 Feb 1999 11:45:01 +1030


Hans-Dieter.Dreier@materna.de wrote:

>> Yes, this would be the way it would be done with object composition.  I
>> think this is the way it would need to be done it a language with
>> object-based inheritance rather than class-based inheritance.
> Could you please explain the difference ?

Sure.  Languages with object-based inheritance, also known as
prototyping, do not have classes, only objects.  Inheritance is done via
the cloning (copying) of objects along with the ability the update
methods (overriding except at run time).

There are two varieties.  Embedding languages copy all the methods into
the new object - delegation languages use the old object as a delegate
object for the methods until they are overridden.  The latter is
basically a sharing agreement.

> To me, that difference "is-a" vs. "has-a" is not as important as it seems
> to be to other people. I think it is mainly caused by the fact that common
> languages emphasize the difference, but "deep underneath" it is just two
> sides of the same concept: Grouping "features" (Eiffel speak) together in
> a slightly different way.

Well from a programming point of view it might be, but not in the "real
world".

> Maybe we could say that "is-a" is a constant grouping (i.e. participants
> do not change during the lifetime of the aggregate), but "has-a" is a
> mutable grouping (i.e. participants may change) ?

Then how do you explain languages with dynamic inheritance (can change
their superobject?) =)

Although it's not necessarily within my conceptual model, I've seen some
good arguments for it, especially within object-based inheritance.
 
> As I see it, object composition (or delegation, or whatever term we use
> for indirect access) allows both "is-a" and "has-a" at a performance
> penalty, where static "dispatch"  just allows "is-a" at a better (runtime)
> performance since it is static and immutable, but imposes rules and
> restrictions due to merging problems.

I think we can safely say we have one object if possible, more if
necessary.  An optimiser can help here - in fact we get back to the
combining object optimisation I referred to, which is the opposite of
this discussion about separating objects.

> How the (possibly combined) name space is to be searched in both cases and
> how name clashes and qualification should to be handled has IMO *nothing*
> to do with the choice between "is-a" and "has-a": It should be handled
> independently ("orthogonal" is the word I like for such an issue). Of
> course, the compiler may choose default strategies dependent on whether it
> is "is-a" or "has-a", if the user does not specify what he wants.

The reason they're generally separated is that you can have more than
one has object, whereas usually you have just one is.  Of course with
multiple inheritance it becomes harder  - we know this by now.

It generally doesn't make sense to say Building.CallMom, whereas
Building.Phone.CallMom is sensible - on the other hand Building.Nuke is
understandable regardless of what parts it has.  It's all about
modelling the universe.

>> I would say rather that inheritance of more than two classes with state
>> is not done too often.  Stateless mixins would not affect the situation
>> of object layout.  Although the method lookup table might be affected in
>> the same way.
> ... for virtual items (for statical dispatch there is no problem, as
> usual).

If an item is not dynamically dispatched on it doesn't need to be in the
table in the first place, certainly.  But to do this you have to make
sure that this method entry could never be dispatched to.  That means
that everywhere this implementation could be used, a static dispatch
could be performed.  It could be that it could only be determined in
some places, here it can't be eliminated.  This sort of elimination
would usually require inter-class optimisation I would think.

> The v-table problem is certainly present, but it can be solved:
> The compiler silently generates an intermediate class with the same
> instance layout and the same v-table layout as the original one, but has
> the this pointer reference a v-table filled with *different* references.
> Where the original contents reflects the static situation of the base
> class without any overriding, the v-table in the intermediate class takes
> that into account.
> 
> (When rereading it, I see that this could not easily be achieved using the
> instance layout shown below. So maybe it is not so good an idea).

I'm not sure what you mean by this.  Why would you need a static
dispatch table?

> IMO if one introduces a language feature (such as MI), it should be 
> universally applicable and not be ridden with exceptions and rules that
> restrict its use and are good for the occasional surprise.

Undoubtably.  Anyway, I'm going to go back as reread everything that has
been said about this, and then try to recap my current thoughts.
 
>> Well it would certainly make it more complicated, that's the nature of
>> adding optimisations.  The problem of incremental compilation being less
>> efficient is certainly present, but that's going to be there if you want
>> to do ANY program-wide optimisation.
> True, but I'd try to avoid *needing* to do program-wide optimisations in
> the first place.

That's true.  You couldn't do MI with the scheme with making it
fragile.  Although I don't see cMI is any different.

The real problem with your cMI proposal as I see it, is that, although
it would avoid this, it would lose the fundamental OOP property that you
could take an reference to an object of class A or B, where B is a
subclass of A, and dispatch on a method of A.

Take a look at your original proposal of cMI.  There is no a in B.  For
this reason, if I had a reference, it's going to be really hard for me
to find the a not knowing what class is being the reference.

> As soon as "optimisations" are no longer transparent to the user, that's
> the point where alarm bells should start ringing to indicate a possible
> design problem.

Why aren't they transparent?  You have a tradeoff between compilation
time and run time.  Both take time and are visible, but you can't say
they affect what the programmer has to do.


>> I'm not exactly sure what this means.  Do you mean converting a type to
>> it's supertype?  If so, normally casting to a supertype would not change
>> the pointer.  It is done purely in the type system and matters only at
>> compile-time.  The same methods are called, and the same class-of is
>> returned.
> 
> I don't agree here. What you say is true only as long as statical dispatch
> can be used, which is not the case for any access to an instance item. If
> the supertype uses a different instance layout, you *need* to change the
> pointer to be able to use any of that supertype's methods which rely on
> their instance layout. Or the compiler must provide different method
> implementations that can use the instance layout of the MI'ed class - but
> that means effectively duplicating a class hierarchy (among other
> disadvantages). Once you changed to pointer, you cannot access the
> original object any longer.

As far as I know, supertypes will always use the same instance layout -
the subtype extends the layout.  Method dispatch would be a nightmare
without it.

I'm not aware of any class-based inheritance languages which allow the
class of an object to be changed at run-time I think you're referring
to.

We don't seem to be communicating here - could you go into more detail?

> AFAIK a table is used only for virtual items, not for statical "dispatch"
> (The term "dispatch" really doesn't suit well since it is an ordinary
> direct function call to a statical adress).

Yes, this is true.  By static dispatch are you referring here to C
"static"?  I'm not, so I just want to make sure.

Few compilers do static dispatch optimising I think, and even in those
that do, there's going to be a lot of methods that can't be statically
dispatched on all of the time.  C non "virtual" items can always by
statically dispatched on, but I've never seen a use for that feature.

>> Basically inheritance is done by methods delegating to other objects.
>> It's all very similar to composition, except that you don't need to
>> syntactically traverse long pointer chains to reference an inherited
>> method.
> I can't see why traverse long pointer chains? I think it can be done like
> this in almost any case:

I meant syntactically, like you'd have to do with cMI.

> If the instance items (e.g. references to "base classes") are constant
> (always the case when MI is intended and no virtuals are involved), the
> compiler can constant-fold them at compile time.

In tMI this would be static dispatch optimising, in cMI a pointer
traversal optimisation.  I've never actually thought or heard of the
latter.

> Otherwise there is one indirection per class hierarchy level involved. How
> deep do you want the class hierarchy to grow?

Yes, since this information could be at a constant location.  This
location would probably need program-wide optimisation to determine
though!

-- 
     Matthew Tuck - Software Developer & All-Round Nice Guy
             mailto:matty@box.net.au (ICQ #8125618)
       Check out the Ultra programming language project!
              http://www.box.net.au/~matty/ultra/