Beruflich Dokumente
Kultur Dokumente
C+
An earlier version of this paper was presented to the European UNIX Users' Group
conference in Helsinki, May 1987. This paper has been revised to match the multiple inheritance scheme that rvas arrived at afler further experimentation and
thought. For more information see Stroustrup [1978; 1989].
1989 367
I. Introduction
This paper describes an implementation of a multiple inheritance
mechanism for Cr+ lstroustrup 1986; 1989]. It provides only the
most rudimentary explanation of what multiple inheritance is in
general and what it can be used for. The particular variation of
the general concept implemented here is primarily explained in
term of this implementation.
First a bit of background on multiple inheritance and C++
implementation technique is presented, then the multiple inheritance scheme implemented for Cr+ is introduced in three stages:
l.
2. Handling of
2. Multiple Inheritance
Consider writing a simulation of a network of computers. Each
node in the network is represented by an object of class switch,
each user or computer by an object ofclass Terminat, and each
communication line by an object of class tine. One way to monitor the simulation (or a real network of the same structure) would
be to display the state of objects of various classes on a screen.
Each object to be displayed is represented as an object of class
Displ.ayed. Objects of class Displ.ayed are under control of a
display manager that ensures regular update of a screen and/or
data base. The classes Terminal and switch are derived from a
class task that provides the basic facilities for co-routine style
behavior. Objects ofclass Task are under control ofa task
manager (scheduler) that manages the real processor(s).
Ideally task and Displ.ayed are classes from a standard
library. If you want to display a terminal class Terminal. must be
derived from class Displ.ayed. Class Terminat, however, is
already derived from class Task. In a single inheritance language,
such as the original version of C++ [Stroustrup 1986] or Simula67,
we have only two ways of solving this problem: deriving Task
from oisptayed or deriving Displ.ayed from task. Neither is
ideal since they both create a dependency between the library versions of two fundamental and independent concepts. Ideally one
would want to be able choose between saying that a Terminal. is a
task and a Displ.ayed; that Line is a oispl.ayed but not a Task;
and that a sh,itch is a Task but not a Displ.ayed.
The ability to express this using a class hierarchy, that is, to
derive a class from more than one base class, is usually referred to
as multiple inheritance. Other examples involve the representation of various kinds of windows in a window system [Weinreb &
Moon l98l I and the representation of various kinds of processors
and compilers for a multi-machine, multi-environment debugger
[Cargill 1986].
In general, multiple inheritance allows a user to combine
independent (and not so independent) concepts represented as
classes into a composite concept represented as a derived class. A
common way of using multiple inheritance is for a designer to
provide sets of base classes with the intention that a user creates
new classes by choosing base classes from each ofthe relevant
sets. Thus a programmer creates new concepts using a recipe like
"pick an A and/or a 8." In the window example, a user might
specify a new kind of window by selecting a style of window
interaction (from the set of interaction base classes) and a style of
Multiple Inheritance lor
C++ 369
t.
3.
C* Implementation
Strategy
ctass A {
int
l,
l.
);
In most ofthis paper data hiding issues are ignored to simplify the discussion and
shorten the examples. This makes some examples illegal. Changing the word ct.ass
to struct would make the examples legal, as would adding pubt ic specifrers in the
appropriate places.
370
a;
void f( int
Bjarne Stroustrup
inta;
A* Pa;
pa->f(2);
is transformed by the compiler to an "ordinary function call":
f --F1A(pa,2)
int
int
int
a;
b;
c;
ctass A {
int a;
,i
C++ 37I
class B : A {
class C : B {
In this
case, a table
int a;
vptr
int b;
int c;
vtbl.:
A::f
B::g
C::h
c* pc;
Pc->g(2);
becomes something like:
(*(pc->vptr
tII))
(Pc,2) ;
{ ... };
ctassBt...);
ctass A
ctassC:4,8t...);
This means that a c is an
define c like this:
and
a B. One might
equivalently2
classC:8,4t...];
2.
372
Except for possible side effects in constructors and destructors (access to global variables, input operations, output operations, etc.).
Bjarne Stroustrup
A part
B part
C part
c* p";
pc->bf(2);
//
I
l/
I
that bf is a member of B
that C has no member named bf
assume
and
Ct+
373
lJe
A part
B::bf's this
B part
part
4.3 Ambigaities
Consider potential ambiguities
member i i:
if both n and
s have a public
c* pc;
pc->ii;
pc-)A::ii;
pc-)B::ii;
ll
ll
Cts A's
Cts Brs
ii
ii
ctassC:ArB{};
c* pc;
pc->f()
37
Bjarne Stroustrup
pc->A::f();
pc->B::f();
ll
ll
Cts A's
C.s B's
f
f
c.
class C: A, B t
);
c* pc;
pc->f()i
ll C:zf is
catted
4.4 Casting
Explicit and implicit casting may also involve modifying a pointer
value with a delta:
c*
Pc;
B* pb;
pb = (B*)pc;
pb = pc,
Pc = Pb;
pc = (C*)pb;
ll pb = (B*)((char*)pc+del.ta(B))
l/ pb = (B*)((char*)pc+detta(B))
I I error: cast needed
ll pc = (C*)((char*)pb-del.ta(B))
pc ..
A part
Pb
...
B part
C part
Multiple Inheritance
r C+
37 5
pc ==
C*pc=0;
B*pb=0;
if (pb =-- 0)
pb = pc;
if (pb == 0)
I/
pb = (B*)((char*)pc+de[ta(B))
The second test would fail since pb would have the value
(B*) ( (char*)0+deI ta(B) ).
The solution is to elaborate the conversion (casting) operation
to test for the pointer-value 0:
C*pc=0;
B*pb=0;
if (pb == 0)
pb = pc,
ll pb = (pc==O)?0:(B*)((char*)pc+del.ta(B))
if (pb == 0)
The added complexity and run-time overhead are a test and an
increment.
376
Bjarne Stroustrup
5. Virtual Functions
Naturally, member functions may be virtual:
f(); ];
f(); virtuaI void g(); ];
f(); };
pa->f ( ) ;
pb->f ( ) i
pc->f ( ) i
All these calls will invoke c: : f ( . This follows directly from the
definition of virtual. since class c is derived from class n and
from class e.
5.1 Implementation
On entry to c: : f , the th i s pointer must point to the beginning of
c object (and not to the e part). However, it is not in general
known at compile time that the s pointed to by pb is part of a c
so the compiler cannot subtract the constant del.ta(B). Consequently del.ta(B) must be stored so that it can be found at run
time. Since it is only used when calling a virtual function the
obvious place to store it is in the table of virtual functions (vtbt).
For reasons that will be explained below the delta is stored with
each function in the vtbl. so that a vtbl. entry will be of the
form:3
the
struct vtb[-entry {
);
void
int
(*fct) ( );
delta;
3.
The AT&T Cl+ 2.0 implementation uses three frelds in this structure, but only the
two shown here are used by the virtual function call mechanism; see Lippman &
Stroustrup [ 1988].
Multipte Inheritance
r C++ 377
vPtr ...
A part
vptr ...
B part
vtbl.:
I C::f |
vtbl.:
I C::f | -del,ta(B)
0
I B::s |
I
I
C part
pb->f()i
ll call of C::f:
// register vtb[-entry* vt = &pb-)vtbttindex(f)t;
I I vt.>fct) ((B*)((char*)pb+vt->detta))
Note that the object pointer may have to be adjusted to point to
the correct sub-object before looking for the member pointing to
the vtbl.. Note also that each combination of base class and
derived class has its own vtbl.. For example, the vtbt for s in c
is different from the vtbl. of a separately allocated a. This
implies that in general an object of a derived class needs a vtbl,
for each base class plus one for the derived class. However, as
with single inheritance, a derived class can share a vtbt with its
first base so that in the example above only two vtbl.s are used for
an object of type c (one for n in c combined with c's own plus
one
for e in
c).
5.2 Ambigaities
The following demonstrates a problem:
ctass
ctass
ctass
C* pc
= new C;
pc->f ( ) i
378
Bjarne Stroustrup
pc->A::f();
pc->B::f();
Explicit qualification "suppresses" virtuat so the last two
calls really invoke the base class functions. Is this a problem?
Usually, no. Either c has an f < > and there is no need to use
explicit qualification or c has no f < and the explicit qualification
is necessary and correct. Trouble can occur when a function f < t
is added to c in a program that already contains explicitly
qualied names. In the latter case one could wonder why someone would want to both declare a function virtual and also call it
using explicit qualifrcation. If f ( ) is virtual, adding an f ( ) to the
derived class is clearly the correct way of resolving the ambiguity.
The case where no c: : t is declared cannot be handled by
resolving ambiguities at the point of call. Consider:
A* pa = pc;
pa->f ( ) i
f(); ];
f(); ];
ll error: C::f
needed
// ambiguous
/l inpLicit conversion of C* to A*
l/ not ambiguous: caIts A::f();
6. Multiple Inclusions
A class can have any number of base classes. For example,
ctass
A:81,82,83,84,85,
{ ... };
ctass A
B, B {
... }; //
error
Multiple Inheritance
r C++ 379
ctassLt...);
ctass A : L t ... );
class B : L { ... };
ctass C : A , B { ... };
In such cases multiple objects of the base class are part of an
object of the derived class. For example, an object of class c has
two L's: one for and one for g:
L part
(of
A)
A part
L part
(of
B)
B part
C part
This can even be useful. Think of I as a link class for a Simulastyle linked list. In this case a c can be on both the list of Es and
the list of ss.
6.2 Naming
Assume that class I in the example above has a member m. How
could a function c: : f refer to t: : m? The obvious answer is "by
explicit qualifr cation" :
6.3 Casting
Consider the example above again. The fact that there are two
copies of I makes casting (both explicit and implicit) between l-*
and c* ambiguous, and consequently illegal:
C* pc = new C;
L* p[ = pc;
pt = (L*)pc;
pt = (L*)(A*)pc;
pc = pt;
pc = (L*)pt i
pc = (C*)(A*)pt i
ll
I/
ll
l/
ll
ll
error: ambiguous
error: sti l. I ambiguous
The L in Crs A
error: ambiguous
error: stitI ambiguous
The C containing A's L
I don't
extern
f(L*);
l/
some
standard function
A aa;
C cc;
f(&aa);
f(&cc);
f((A*)&cc);
I
I
I
I fine
I error:
I fine
ambiguous
Ct+
381
7. Virtual Base
Classes
\/hen a class c has two base classes R and s these two base
classes give rise to separate sub-objects that do not relate to each
other in ways different from any other and e objects. I call this
independent multiple inheritance. However, many proposed uses
of multiple inheritance assume a dependence among base classes
(for example, the style of providing a selection of features for a
window described in $2). Such dependencies can be expressed in
terms of an object shared between the various derived classes. In
other words, there must be a way of specifying that a base class
must give rise to only one object in the final derived class even if
it is mentioned as a base class several times. To distinguish this
usage from independent multiple inheritance such base classes are
specified to be virtual:
ctass Atl
ctass Btl
ctass Ct,
: virtual h, t ... ];
: virtuaI t., t ... ];
: Ahl , Bt, { ... };
cIassA:virtuatLt...
ctassB:virtuatLt...
ctassC:A,B{...};
ctassD:L,C{...};
A
);
);
382
Bjarne Stroustrup
derived from a common virtual base is as alternative and composable interfaces to the virtual base.
7.1 Representation
The object representing a virtual base class u object cannot be
placed in a frxed position relative to both w and st, in all objects.
Consequently, a pointer to ll must be stored in all objects directly
accessing the u object to allow access independently ofits relative
position. For example:
Ab'l* paul
= new Al;
= new Bl'f;
Cl'l* pcw = new Clll
g*
pbw
paH . ->
At',
part
tl part
lv
l<.......
I
I
pbt{ ..>
BLJ
hl
part
part
lv
l<.......
I
I
C++ 383
pctJ ..>
All part
Bhrl
part
v
Cll part
Ll
part
l<
I
I
'
,,
instead
... ];
f(); ... };
384
Bjarne Stroustrup
ctass I'l {
virtuat
virtuaI
virtuaI
virtuat
void
void
void
void
f();
g();
h()i
k()i
);
ctass AUI : virtuat tl { void g(); ... };
ctass Bll : virtual H { void f(); ... };
ctass CLJ : Ahl , Bh, { void h(); ... };
GLj* pc
= new Clli
pcu-)f()i
pcw->g();
pcw->h();
((Alt*)pcw)->f
cw
ll
ll
ll
I/
();
Bw:.zf('t
Atl==g(,
C\l=zh()
Bht::f ();
. I
vl
Al'l
part
. I
vl
BL,
part
Cl'l
part
...>l
vptr
I
I
hl
part
vtbt:
l
I
I
|
BU::f
de L ta ( Bhl) -de t
Ct'l: : h
-de L ta (l'l)
-de t ta (t'l)
l::k
AH::s
ta(l)
Multiple Inheritance
r C++ 385
in cu, an ambiguity
Ah,{s}
Br{tf}
I
Note that a call "up" through one path of the DAG to a virtual
function may result in the call of a function (re-defrned) in
another path (as happened in the call <(Aw*)pcw)->tcl in the
example above).
ctass
' , .-.
-f() t
protec ted:
publ.ic:
my
stuff )
f() { -f(); }
);
Each class provides a protected function doing "its own stufl"
-f ( ), for use by derived classes and a public function f < I as the
interface for use by the "general public."
386
Bjarne Stroustrup
ctass A
: pubtic virtuat
u ...
protected:
-f() {
pubLic:
);
u...
mY
I'l
stuff }
u ...
protected:
-f() t
pubt i c:
);
u ...
my
stuff )
Multiple Inheritance
r C++ 387
ctassA{A(int);}i
ctassBtB(int);)i
ctass C: A, virtual B {
C(int a, int b) : A(a), B(b) t...
);
v v(1);
a(2);
b(3)
c c(4) i
A
B
ll
//
l/
l/
}i
t,
use v(int)
use v(int)
use V()
use v()
9. Access Control
The examples above ignore access control considerations. A base
class may be pubtic or private. In addition, it may be virtuat.
For example:
class
388
Bjarne Stroustrup
: 81
ll private (by defautt),
non-virtua[ (by def aul.t)
/I
, virtuaI 82 // private (by defaul.t), virtuaI
, pubtic 83 ll public, non-virtual
//(by defauLt)
);
, pubtic virtual.
il ...
84 {
ctass C
publ.
ic A, B t ... );
10. Overheads
The overhead in using this scheme is:
l.
2. One word per function in each vtbt (to hold the delta).
3. One memory reference and one subtraction for each call of
a virtual function.
4. One memory reference and one subtraction for access of a
base class member of a virtual base class.
Note that overheads l. and 4. are only incurred where multiple
inheritance is actually used, but overheads 2. and 3. are incurred
for each class with virtual functions and for each virtual function
call even when multiple inheritance is not used. Overheads l. and
4. are only incurred when members of a second or subsequent
base are accessed "from the outside"; a member function of a virtual base class does not incur special overheads when accessing
members of its class.
This implies that except for 2. and 3. you pay only for what
you actually use; 2. and 3. impose a minor overhead on the virtual
function mechanism even where only single inheritance is used.
This latter overhead could be avoided by using an alternative
implementation of multiple inheritance, but I don't know of such
an implementation that is also faster in the multiple inheritance
case and as portable as the scheme described here.
Multiple Inheritance
r C++ 389
11.
But is it Simple to
Use?
l.
Lots of rules.
l.
A base class.
2. A virtual base class.
These are two ways of creating/specifying a new class rather than
ways of creating two different kinds of classes. The rules for using
the resulting classes do not depend on how the name space was
extended:
l.
2. Rules for
for single
inheritance.
390
Bjarne Stroustrup
l.
You can extend a class's name space more than once (with
more than one base class).
2. You can extend a class's name space in two ways rather
than in only one way.
In addition, care must be taken to take the sharing into
account when programming member functions of classes with virtual base classes; see section 7 above.
This appears minimal and constitutes an attempt to provide a
formal and (comparatively) safe set of mechanisms for observed
practices and needs. I think that the scheme described here is "as
simple as possible, but no simpler."
A potential source of problems exists in the absence of o'system provided back-pointers" from a virtual base class to its
enclosing object.
In some contexts, it might also be a problem that pointers to
sub-objects are used extensively. This will affect programs that
use explicit casting to non-object-pointer types (such as char*)
and "extra linguistic" tools (such as debuggers and garbage collectors). Otherwise, and hopefully normally, all manipulation of
object pointers follows the consistent rules explained in $4, $7,
and $8 and is invisible to the user.
12. Conclusions
Multiple inheritance is reasonably simple to add to Cr+ in a way
that makes it easy to use. Multiple inheritance is not too hard to
implement, since it requires only very minor syntactic extensions,
and frts naturally into the (static) type structure. The implementation is very efficient in both time and space. Compatibility with
C is not affected. Portability is not afected.
Multipte Inheritance
r C++
391
Acknowledgements
In
392
Bjarne Stroustrup
Appendix
The original multiple inheritance des as presented to the EUUG
conference in Helsinki in May 1987 contained a notion of delegation.a Briefly, a user was allowed to specify a pointer to some
class among the base classes in a class declaration and the object
thus designated would be used exactly as if it was an object
representing a base class. For example:
q->f()i
p;
int ci
intb;
Gul Agha, An Overview of Actor languages, SIGPLAN Notices, pp. 58-7, October
I 986.
C++ 393
394
Bjarne Stroustrup
References
Tom Cargill, PI: A Case Study in Object-Oriented Programming,
OOPSIA'86 Proceedings, pages 350-360, September 1986.
in Ct+,
Proc.
1989.
4,
28,
2,
19891
C++ 395