[comp.lang.c++] Problem with multiple inheritance?

tonyw@microsoft.UUCP (Tony Williams) (11/02/88)

In article <3438@jpl-devvax.JPL.NASA.GOV> david@beowulf.JPL.NASA.GOV (David Smyth) writes:
>only use multiple-inheritance when you have the source of all classes
>back to their roots because a client can introduce bugs which a C++
>compliler won't catch; etc. etc. etc.

Is this described anywhere in the literature?  Can anyone tell me about
the problems referred to here?
   Tony

david@beowulf.JPL.NASA.GOV (David Smyth) (11/03/88)

tonyw@microsoft.UUCP (Tony Williams) writes:
>david@beowulf.JPL.NASA.GOV (David Smyth) writes:
>>only use multiple-inheritance when you have the source of all classes
>>back to their roots because a client can introduce bugs which a C++
>>compliler won't catch; ...
>
>Is this described anywhere in the literature?  Can anyone tell me about
>the problems referred to here?

The problem was discussed in several sessions at the USENIX C++ conference
in Denver two weeks ago, including the tutorials given by Jonathan Shopiro.

The concept is: any time a class exists more than once in a multiple
inheritance heirarchy, then that class should be declared as "virtual."
Shopiro gave the following example:

class List { ... };	// Something which can be on a list.
class Window { ... };	// A window.

class Bordered : 
	public List, virtual public Window 
	{ ... };	// A bordered window on a list.

class Labeled : 
	public List, virtual public Window 
	{ ... };	// A labeled window on a list.

class Fancy :
	public Bordered, public Labeled
	{ ... };	// A bordered, labeled window on TWO lists - 
			// clear as mud, right?

And here is more illumination: if the object library author did not
specify that Window was a virtual base class of Bordered and Labeled,
but just a base class, then Fancy would consist of TWO windows!  You
would not have one with characteristics (member data and member
functions) of both, rather you would have a new class with a Bordered
window, and a Labeled window.

Isn't that just so obvious?  Won't all of your average programmers
simply have a wonderful time with this new feature? :-(  Note that
the object library author must consider which class may be combined with
which other classes, and then declare any intersections of their
inheritance heirarchies to be virtual - well, if you want that part
of their characteristics to combine, any way.  Simple, isn't it? :-(

Note that the derived classes change radically when a base class is
altered - Fancy will be on a single list, not two, if the definition of
EITHER Bordered or Labeled is changed to make List a virtual base
class.  That is theoretically OK, since any change to a base class may
impact any derived classes.  Practically, however, it is a can of worms.

Other problems: when Fancy overrides a Window method, its OK, because
Fancy has only one Window.  If it overrides a List method, nope:
ambiguous.  True, the compiler says so.  But the author of Fancy
must know the inheritance of the classes in the library to make
sure the appropriate method is overriden.  Notice that simply a
complete list of names visible in the Bordered and Labeled classes
is not enough: the client (Fancy) author must also know the internal
structure of the library.  Not very re-usable.

And more: you lose substitutablity when you use a virtual base class.
Fancy cannot be on a list of windows, because you can't cast a pointer
declared as a pointer to Window into a pointer to Fancy, or Bordered
or Labeled for that matter.  Again, not very useful.  Imagine this
bug: the window system object library has a Refresh method, which
causes all windows to repaint.  A client creates a new class with
Window a virtual base class, and overrides the refresh method (of course).
Hmm.  That window won't refresh.  How nicely obscure. :-(

What to use instead: Member objects!

class List 	{ ... };	// exactly as above.
class Window 	{ ... };	// ditto.
class Border 	{ ... };	// Border attrs and methods.
class Label	{ ... };	// Label attrs and methods.

class Bordered : Window		// Inherit most significant characteristics
	{ List borderedList; 
	  Border myBorder;
	... }; 		// Window with border, on a list.

class Labeled : Window
	{ List labeledList;
	  Label myLabel;
	... };		// Window with label, on a list.

class Fancy : Window
	{ List labeledList, borderedList;
	  Border myBorder;
	  Label  myLabel;
	... };		// Labeled, bordered window on two lists.

True, more classes, and that is always a pain.  
TANSTAAFL - There aint no such thing as a free lunch.  There is
such a thing as obscurity however.  Neither member objects nor
multiple inheritance provide a free lunch, but only one is obscure...
Let your Joe Average programmers prove it to you!

db@lfcs.ed.ac.uk (Dave Berry) (11/11/88)

Caveat: the following article is based on my general knowledge of programming
languages, my experience with C++, and David Smyth's article.  I may have
missed the point on several occasions.

In article <3451@jpl-devvax.JPL.NASA.GOV> david@beowulf.JPL.NASA.GOV (David Smyth) writes:
>
>Shopiro gave the following example:
>
>class List { ... };	// Something which can be on a list.
>class Window { ... };	// A window.
>
>class Bordered : 
>	public List, virtual public Window 
>	{ ... };	// A bordered window on a list.
>
>class Labeled : 
>	public List, virtual public Window 
>	{ ... };	// A labeled window on a list.
>
>class Fancy :
>	public Bordered, public Labeled
>	{ ... };	// A bordered, labeled window on TWO lists - 
>			// clear as mud, right?

This makes sense to me.  Like any language construct, the details have
to be explained (think of call-by-value vs. call-by-reference vs.
call-by-name procedure parameters etc.), but this construct fits with
the rest of the way C++ does things.

>And here is more illumination: if the object library author did not
>specify that Window was a virtual base class of Bordered and Labeled,
>but just a base class, then Fancy would consist of TWO windows!  You
>would not have one with characteristics (member data and member
>functions) of both, rather you would have a new class with a Bordered
>window, and a Labeled window.

Usually the sharing of objects in any hierarchy has to be specified explicitly.
There isn't a rule that will always give the desired results, because
the desired results vary from case to case.

>Note that the derived classes change radically when a base class is
>altered - Fancy will be on a single list, not two, if the definition of
>EITHER Bordered or Labeled is changed to make List a virtual base
>class.  That is theoretically OK, since any change to a base class may
>impact any derived classes.  Practically, however, it is a can of worms.

What I find odd is the assumption that all occurrences of a virtual
class will be the same.  In an analogous hierarchy in ML the user
can specify which sub-components are shared (i.e. the same), to any
depth in the visible hierarchy.  I'd expected C++ to offer the same
facility.  In the above example, I would have expected the programmer
to have to write  "sharing Bordered.List == Labeled.List" if he wanted
Fancy objects to be on only one list.  This might have gone some way to
reducing unexpected effects from changes in base classes.

Incidentally, our ML group finds that hierarchies often have a rhomdoid
shape, with the "classes" at the top of the rhombus specifying lots
of sharing between those in the middle.  Someone using the rhombus
as a whole rarely needs to know its internal hierarchy.  If the same
effect applies to C++, the effects may not be as bad as David expects.

(To prevent misunderstanding, let me stress that the hierarchy of
structures in ML is analogous to an inheritance hierachy, but is
substantially different.  Also, I'm comparing the languages, just
one feature from each.)

>Other problems: when Fancy overrides a Window method, its OK, because
>Fancy has only one Window.  If it overrides a List method, nope:
>ambiguous.

This is obvious...

>True, the compiler says so.  But the author of Fancy
>must know the inheritance of the classes in the library to make
>sure the appropriate method is overriden.  Notice that simply a
>complete list of names visible in the Bordered and Labeled classes
>is not enough: the client (Fancy) author must also know the internal
>structure of the library.  Not very re-usable.

So you need more documentation to use a library.  How does this stop
it from being re-usable?

>And more: you lose substitutablity when you use a virtual base class.
>Fancy cannot be on a list of windows, because you can't cast a pointer
>declared as a pointer to Window into a pointer to Fancy, or Bordered
>or Labeled for that matter.

Now this is a problem.  Can't C++ introduce a notion of cast that is
more sophisticated than the C notion, to handle cases like this?

>What to use instead: Member objects!
>
>class List 	{ ... };	// exactly as above.
>class Window 	{ ... };	// ditto.
>class Border 	{ ... };	// Border attrs and methods.
>class Label	{ ... };	// Label attrs and methods.
>
>class Bordered : Window		// Inherit most significant characteristics
>	{ List borderedList; 
>	  Border myBorder;
>	... }; 		// Window with border, on a list.
>
>class Labeled : Window
>	{ List labeledList;
>	  Label myLabel;
>	... };		// Window with label, on a list.
>
>class Fancy : Window
>	{ List labeledList, borderedList;
>	  Border myBorder;
>	  Label  myLabel;
>	... };		// Labeled, bordered window on two lists.

This is certainly a simpler structure.  

If multiple inheritance doesn't provide the possibility of lists of
a given type including objects of a sub-type of that type, with
virtual members working properly, I don't think multiple inheritance
adds much, if anything, to the language.  I guess it make function
calls shorter (you don't have to qualify the member name with the
class name(s)), but this hardly justifies the increased complexity.

If proper lists were provided, then Jon's multiple inheritance structure
would provide more facilities than David's member objects structure.
The choice of which to use would depend on what facilities were needed.

Having gone on at such length, I hope I've got this right...
Dave Berry,	Laboratory for Foundations of Computer Science, Edinburgh.
		db%lfcs.ed.ac.uk@nss.cs.ucl.ac.uk
		<Atlantic Ocean>!mcvax!ukc!lfcs!db

bs@alice.UUCP (Bjarne Stroustrup) (11/19/88)

Dave Berry, Laboratory for Foundations of Computer Science, Edinburgh.
beowulf.JPL.NASA.GOV (David Smyth):

 > >And more: you lose substitutablity when you use a virtual base class.
 > >Fancy cannot be on a list of windows, because you can't cast a pointer
 > >declared as a pointer to Window into a pointer to Fancy, or Bordered
 > >or Labeled for that matter.
 > 
 > Now this is a problem.  Can't C++ introduce a notion of cast that is
 > more sophisticated than the C notion, to handle cases like this?


Try virtual functions. Naturally, I could make casts work from a virtual
base class to a derived class (where the derived class is unambiguously
specified), but I don't see much point in such an elaboration since we
already have virtual functions as a cleaner mechanism.

 > If multiple inheritance doesn't provide the possibility of lists of
 > a given type including objects of a sub-type of that type, with
 > virtual members working properly, I don't think multiple inheritance
 > adds much, if anything, to the language.

Exactly, so it does.