[comp.std.c++] Adding multiple inheritance to a single inheritance class library.

gaa@castle.ed.ac.uk (Gerard A. Allan) (06/12/91)

Many C++ libraries have a common root class and as a consequence they
contain many functions and methods that take the root class as an
argument requiring a cast to the desired type inside the function.
This can cause a problem when attempting to use multiple inheritance and
a virtual base class.
eg. 
	class Root {
		virtual void function( Root *r)=0;
	};
	class Derived : public Root {
		int x;
		virtual void function( Root *r);
	};
	void Derived::function( Root *r)
	{
		Derived *n=(Derived *)r;
		n->x++;
	}

if I now define Derived as,
			class Derived : virtual public Root {
			int x;
			virtual void function( Root *r);
			};

In preparation to make,
			class mine : virtual public Root {};
			class multi : public mine, public Derived {};

The function Derived::function(Root *r) no longer works as there is an
error   "cannot cast up from virtual baseclass Root"

This is of course perfectly true (ARM 10.6c) "Casting form a virtual
base class to a derived class is disallowed to avoid requiring an
implementation to maintain pointers to enclosing objects". This
makes it very difficult to use multiple inheritance and virtual bases 
from a library with a single root class. 

Is there some way round this problem so that I can combine classes in a
"natural" way ? After all, the cast is not ambiguous since there is only
one Derived. What solutions to this problem have C++ programmers
developed or is multiple inheritance incompatible with the single
inheritance methodology ? And finally, is there a case for requiring
"an implementation to maintain pointers to enclosing objects" ?

I'd be interested in hearing how programmers have dealt with this
problem. 

Gerard A. Allan                              | Post:  EMF
gaa@castle.ed.ac.uk                          |        Kings Buildings
JANET:gaa@uk.ac.ed.castle                    |        University of Edinburgh
Internet:gaa%castle.ed.ac.uk@cunyvm.cuny.edu |        Edinburgh
EARN/BITNET:gaa%castle.ed.ac.uk@UKACRL       |        Scotland
UUCP:gaa%castle.ed.ac.uk@ukc.uucp            |        EH9 3JL

sakkinen@jyu.fi (Markku Sakkinen) (06/14/91)

(Arrrgh: I already wrote and submitted something like this yesterday,
but some testing of the news system here caused all articles to get lost.)

In article <10971@castle.ed.ac.uk> gaa@castle.ed.ac.uk (Gerard A. Allan) writes:
>
>Many C++ libraries have a common root class and as a consequence they
>contain many functions and methods that take the root class as an
>argument requiring a cast to the desired type inside the function.
>This can cause a problem when attempting to use multiple inheritance and
>a virtual base class.
>eg. 
>	class Root {
>		virtual void function( Root *r)=0;
>	};
>	class Derived : public Root {
>		int x;
>		virtual void function( Root *r);
>	};
>	void Derived::function( Root *r)
>	{
>		Derived *n=(Derived *)r;
>		n->x++;
>	}
>
>if I now define Derived as,
>			class Derived : virtual public Root {
>			int x;
>			virtual void function( Root *r);
>			};
>
>In preparation to make,
>			class mine : virtual public Root {};
>			class multi : public mine, public Derived {};
>
>The function Derived::function(Root *r) no longer works as there is an
>error   "cannot cast up from virtual baseclass Root"
>
>This is of course perfectly true (ARM 10.6c) "Casting form a virtual
>base class to a derived class is disallowed to avoid requiring an
>implementation to maintain pointers to enclosing objects". This
>makes it very difficult to use multiple inheritance and virtual bases 
>from a library with a single root class. 

Have you realised how utterly dangerous your 'Derived::function' is
in the first place?   From one viewpoint it is a blessing that virtual
base classes prevent such casts, which can cause any amount of havoc.
There is no assurance that actual arguments will in fact refer to
Derived objects, unless 'function' is redefined in no other class
in your whole programme.

More object-oriented languages than C++ (Simula, Eiffel, ...) typically
enforce run-time checking of such casts to make them safe; in C++ such
checking is not even possible.  It was a deliberate (and in my opinion
unfortunate) desing decision in C++ that all objects do not carry
sufficient type information like they do in those others languages.

Your problem is aggravated by the fact that it is hard to think of
any sensible example of multiple public inheritance with _non-virtual_
base classes.  Exceptions, of course, are those base classes that are
not inherited over more than one path by any non-immediate descendant.

>Is there some way round this problem so that I can combine classes in a
>"natural" way ? After all, the cast is not ambiguous since there is only
>one Derived. What solutions to this problem have C++ programmers
>developed or is multiple inheritance incompatible with the single
>inheritance methodology ? And finally, is there a case for requiring
>"an implementation to maintain pointers to enclosing objects" ?

With type information in each object, there would be no need for
the restriction.  Eiffel seems to have no sweat with situations like this.
In C++ there is hardly any other solution than to hand-code an
additional level of object-orientation and be sure to use it in all
appropriate classes.  I suppose some of the large general-purpose
C++ class libraries have been built that way.

----------------------------------------------------------------------
"All similarities with real persons and events are purely accidental."
      official disclaimer of news agency New China

Markku Sakkinen (sakkinen@jytko.jyu.fi)
       SAKKINEN@FINJYU.bitnet (alternative network address)
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
----------------------------------------------------------------------

pena@brainware.fi (Olli-Matti Penttinen) (06/17/91)

In article <1991Jun14.113736.3795@jyu.fi> sakkinen@jyu.fi (Markku Sakkinen) writes:

   In article <10971@castle.ed.ac.uk> gaa@castle.ed.ac.uk (Gerard A. Allan) writes:
   >
   >Many C++ libraries have a common root class and as a consequence they
   >contain many functions and methods that take the root class as an
   >argument requiring a cast to the desired type inside the function.
   >This can cause a problem when attempting to use multiple inheritance and
   >a virtual base class.

   [ ex. deleted ]

   >This is of course perfectly true (ARM 10.6c) "Casting form a virtual
   >base class to a derived class is disallowed to avoid requiring an
   >implementation to maintain pointers to enclosing objects". This
   >makes it very difficult to use multiple inheritance and virtual bases 
   >from a library with a single root class. 

   Have you realised how utterly dangerous your 'Derived::function' is
   in the first place?   From one viewpoint it is a blessing that virtual
   base classes prevent such casts, which can cause any amount of havoc.
   There is no assurance that actual arguments will in fact refer to
   Derived objects, unless 'function' is redefined in no other class
   in your whole programme.

Yes, libraries like NIH, which *VERY* much depend on up-casting are
indeed troublesome when combined with multiple inheritance (MI).  They
always enforce the user to follow some additional protocol to make the
classes work with the rest of the world.

To circumvent (sp?) the problem, there are very few alternatives with
current C++ implementations.  The best approach I've found is to view
non-virtual derivation as inclusion of the base object (with added
complexity due to virtual functions, of course) and virtual derivation
as indirectly including (referring to) a common instance of a base
class object.  TODO: the client code should use its natural type
hierarchies to define the appropriate classes, but instead of deriving
from (say NIH) existing classes with the aforementioned behavior a
pointer to such an object should be defined as a private member.

Another problem arises: who should construct the subobject? The best
answer is found in ARM 12.6.2: the most derived class is responsible.
At times it is somewhat difficult to ensure at compile time that any
given class is indeed a leaf class.  An additional boolean member
could be included to indicate whether the (conseptually) virtual base
object has been initialized.

Also, watch out for assignment!  Most (all, I think) current
implementations incorrectly assign virtual base objects multiple times
(once per occurence). Sometimes it only degrades performance, at other
times can be disastrous.

   More object-oriented languages than C++ (Simula, Eiffel, ...) typically
   enforce run-time checking of such casts to make them safe; in C++ such
   checking is not even possible.  It was a deliberate (and in my opinion
   unfortunate) desing decision in C++ that all objects do not carry
   sufficient type information like they do in those others languages.

There has been (and still is) discussion on the subject.  On the one
hand, run time type information would be desirable, not only for
typesafe runtime polymorhism but for high level debugging purposes, as
well.  On the other hand, any additional hidden overheads definitely
are not in "the spirit of C", which C++ tries to honor.

   >Is there some way round this problem so that I can combine classes in a
   >"natural" way ? After all, the cast is not ambiguous since there is only
   >one Derived. What solutions to this problem have C++ programmers
   >developed or is multiple inheritance incompatible with the single
   >inheritance methodology ? And finally, is there a case for requiring
   >"an implementation to maintain pointers to enclosing objects" ?

   With type information in each object, there would be no need for
   the restriction.  Eiffel seems to have no sweat with situations like this.
   In C++ there is hardly any other solution than to hand-code an
   additional level of object-orientation and be sure to use it in all
   appropriate classes.  I suppose some of the large general-purpose
   C++ class libraries have been built that way.

A better way to solve the problem altogether would be templates.  With
them, the libraries could be got right in the first place.  Code size
would of course increase because of numerous duplications, but that
shouldn't be such a problem with current virtual memory techniques.
At least that would clearly be the method of choice from a large scale
software engineering viewpoint, whatever that may be :-)

==pena
--
Olli-Matti Penttinen <pena@brainware.fi> | "When in doubt, use brute force."
Brainware Oy                             |    --Ken Thompson
P.O.Box 330                              +----------------------------------
02151  ESPOO, Finland       Tel. +358 0 4354 2565       Fax. +358 0 461 617