More On Multiple Inheritance

Matthew Tuck matty@box.net.au
Mon, 22 Feb 1999 00:04:57 +1030


Hans-Dieter Dreier wrote:

> First let me define how I understand the term "MI" here:
> It means that you declare multiple base classes which are
> somehow "equal" in the sense that none of the base classes
> is special compared to the others.

This is how I would prefer it - I think though that there are some
languages that use order to define which methods shadow over the others.

> It means also that you do not have to take special steps
> in the base classes to make them MI ready.

Agree, like C++'s "virtual" feature.

> This is quite kludgey, but it should be possible to achieve
> the same results as with MI, though use of the common base class
> instance is more complicated since you got to name the reference
> explicitly.

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.

> For me, the emulated version has the following advantages:
> 1. It is perfectly clear what happens, especially the fact
> that the base class instance is really shared.
> IMO this is better than an implicit assumption.
> Maybe sometimes you don't want sharing; how do you specify
> which case you want, using tMI?

I previously said I couldn't see a reason why you wouldn't want them
shared, but after that read it said that not sharing is sometimes what
is needed.  I haven't been able to track down any examples yet to see
whether they would be better modelled with object composition anyway (I
tend to favour inheritance for is-a relationships and composition for
is-part-of relationships).

Ideally you would define what sort of sharing you would have in the
inheritance list.  I don't see that it's easy to do though.
 
> 2. Implementations of the base classes are completely
> independent from derived classes. They all can use the
> same memory layout for their instances. For tMI,
> this might not be the case (see discussion below).
> 
> If we do agree here (at least in principle),
> let's discuss how MI could be implemented.
> 
> Storing "on both sides of the pointer":
> ---------------------------------------
> 
> As I understand this, it could result in a memory
> layout like this:
> 
>  c
> "this"->a
>  b
>  d
>  e
> 
> The memory layout of D has a gap (b)
> because it was actually "triple inheritance"
> (at least till the advent of n-dimensional memory ;).
> If you just needed "double inheritance",
> it could have been done without a gap.
> 
> That in itself could be regarded as a minor nuisance;
> maybe more-than-double inheritance is not used very
> often.

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.

> What IMO is more important is the fact
> that the implementations of C and D must know
> about the existence of sibling classes
> ...
> I don't like this because it is sure to make
> compiler construction more complicated and
> incremental compilation less efficient.

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.

> And what shall we do if a recompilation cannot
> be done (say, only the binary is present)?

Well, if by binary you mean the executable program, nothing, like usual.
 
> This means that once you have downcasted
> (even implicitly), you can no longer access
> the original object, using the modified pointer.
> I can't quite explain why, but I don't feel
> comfortable with this implication.
> Maybe this breaks covariance. It certainly
> breaks this "classOf" operator I proposed
> in some former posting, which was supposed
> to fetch the original (i.e. non-casted)
> class of some object.

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.

> P2. Having a pointer not point to the start
> of an object makes memory management more
> complicated. In fact, it breaks a central
> assumption that I deem neccessary for easy
> and efficient memory management:
> That the start of the memory block can be
> determined if only a reference to the object
> is known.

I would imagine a field would help this be found.  Either a beginning
pointer, or an offset (slower but allows moving in memory).  Both are of
course slower than a direct pointer, and may well not be worth the gain.

> Internally using the "cMI" approach:
> ------------------------------------
> 
> There is a memory penalty as well as a execution
> time penalty because pointers must be used
> ...

Yes, this would be a huge hit.  Smalltalk does this sort of thing for
method lookups.  It has to since it has dynamic inheritance where the
relationships can change at run-time.  But C++ on the other hand, has a
table for a class with each method, inlcuding inherited ones.  It is
bigger, but a lot faster.  Also, some of the tables could possibly be
joined (optimised) together.

> Other approaches for tMI:
> I'd be glad to learn about them.

Well delegation inheritance does basically what you suggest with object
composition implementation-wise, although it still can have type-MI. 
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.  Embedding inheritance is a similar technique.  You could find
more about these in "A Theory Of Objects" by Abadi and Cardelli, if you
can get your hand on it.  There's plenty of research in this area, I
haven't compiled many references yet though.

> The question should be how to design the
> language to implement cMI with minimum effort
> on the class designer's side, and how to
> minimize impacts on the source code when adding
> or removing MI from a class.

The real goal is to design it to make it easy and flexible.  The answer
is obviously somewhere in between, the question is where.

> I already sketched a possible way in a former posting:
> Introduction of "transparent" names.

Sorry, I should have picked this up earlier, it's pretty similar to
object-based inheritance, a la embedding and delegation.  Some languages
that come to mind are Self, Agora, Cecil & Omega.  There are other
implications of object-based inheritance other than this too, which I
won't go into here.

-- 
     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/