Topic: Adding multiple inheritance to a single inheritance class library.
Author: gaa@castle.ed.ac.uk (Gerard A. Allan)
Date: 11 Jun 91 18:55:12 GMT Raw View
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
Author: sakkinen@jyu.fi (Markku Sakkinen)
Date: 14 Jun 91 11:37:36 GMT Raw View
(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
----------------------------------------------------------------------
Author: pena@brainware.fi (Olli-Matti Penttinen)
Date: 17 Jun 91 08:10:41 GMT Raw View
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