12. Multiple Inheritance

Because you have a mother and a father :-)

comp.lang.c++

Timing of multiple inheritance — ordinary base classes — virtual base classes — the dominance rule — the object layout model — casting from a virtual base — method combination — the multiple inheritance controversy — delegation — renaming — base and member initializers.

12.1 Introduction

In most people’s minds multiple inheritance, the ability to have two or more direct base classes, was the feature of 2.0. I disagreed at the time because I felt that the sum of the improvements to the type system was of far greater practical importance.

Also, adding multiple inheritance in Release 2.0 was a mistake. Multiple inheritance belongs in C++, but is far less important than parameterized types – and to some people, parameterized types are again less important than exception handling. As it happened, parameterized types in the form of templates appeared only in Release 3.0, and exceptions even later. I missed parameterized types much more than I would have missed multiple inheritance.

There were several reasons for choosing to work on multiple inheritance at the time: The design was further advanced, multiple inheritance fits into the C++ type system without major extensions, and the implementation could be done within Cfront. Another factor was purely irrational. Nobody seemed to doubt that I could implement templates efficiently. Multiple inheritance, on the other hand, was widely supposed to be very difficult to implement efficiently. For example, in a summary of C++ in his book on Objective C Brad Cox actually claimed that adding multiple inheritance to C++ was impossible [Cox, 1986]. Thus, multiple inheritance seemed more of a challenge. Since I had considered multiple inheritance as early as 1982 (§2.13) and found a simple and efficient implementation technique in 1984,1 couldn’t resist the challenge. I suspect this to be the only case in which fashion affected the sequence of events.

In September 1984, I presented the C++ operator overloading mechanism at the IFIP WG2.4 conference in Canterbury [Stroustrup, 1984b]. There, I met Stein Krog-dahl from the University of Oslo, who was just finishing a proposal for adding multiple inheritance to Simula [Krogdahl,1984]. His ideas became the basis for the implementation of ordinary multiple base classes in C++. He and I later learned that the proposal was almost identical to an idea for providing multiple inheritance in Simula. Ole-Johan Dahl considered multiple inheritance in 1966 and rejected it because it would have complicated the Simula garbage collector [Dahl, 1988].

12.2 Ordinary Base Classes

The original and fundamental reason for considering multiple inheritance was simply to allow two classes to be combined into one in such a way that objects of the resulting class would behave as objects of either base class [Stroustrup, 1986]:

"A fairly standard example of the use of multiple inheritance would be to provide two library classes displayed and task for representing objects under the control of a display manager and co-routines under the control of a scheduler, respectively. A programmer could then create classes such as

class my_displayed_task : public displayed, public task {
    // ...
};

class my_task : public task { // not displayed
    // ...
};

class my_displayed : public displayed {  // not a task
    // ...
};

Using (only) single inheritance, only two of these three choices would be open to the programmer.”

At the time, I was worried about library classes growing too large ("bloated with features”) by trying to serve too many needs. I saw multiple inheritance as a potentially important means of organizing libraries around simpler classes with fewer interclass dependencies. The task and displayed example shows a way of representing concurrency and output by distinct classes without putting an added burden on application programmers.

"Ambiguities are handled at compile time:

class A { public: void f(); /* ... */ };
class B { public: void f(); /* ... */ };
class C : public A, public B { /* no f()...*/};

void g()
{
    C* p;
    p->f(); // error: ambiguous
}

In this, C++ differs from the object-oriented Lisp dialects that support multiple inheritance [Stroustrup, 1987].”

Resolving such ambiguity by an order dependence, say, by preferring A::f because A comes before B in the base class list, was rejected because of negative experience with order dependences elsewhere; see §11.2.2 and §6.3.1. I rejected all forms of dynamic resolution beyond the use of virtual functions as unsuitable for a statically typed language intended for use under severe efficiency constraints.

12.3 Virtual Base Classes

Paraphrasing [Stroustrup, 1987]:

“A class can appear more than once in an inheritance DAG (Directed Acyclic Graph):

class task : public link { /* ... */ };
class displayed : public link { /* ... */ } ;
class displayed_task
    : public displayed, public task { /* ... */ };

In this case, an object of class displayed_task has two sub-objects of class link:task::link and displayed::link. This is often useful, as in the case of an implementation of lists requiring each element on a list to contain a link element. This allows a displayed_task to be on both the list of displayeds and the list of tasks at the same time.”

Or graphically, showing the sub-objects needed to represent a displayed_task:

Image

I don’t consider this style of list ideal in all situations, but where it fits, it is usually optimal, and I would hate to see it prohibited. Thus, C++ supports the example above. By default, a base class appearing twice will be represented as two sub-objects. However, there is another possible resolution [Stroustrup, 1987]:

“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). 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:

class AW : public virtual W { /* ... */ };
class BW : public virtual W { /* ... */ };
class CW : public AW, public BW { /* ... */ };

A single object of class W is to be shared between AW and BW; that is, only one W object must be included in CW as the result of deriving CW from AW and BW. Except for giving rise to a unique object in a derived class, a virtual base class behaves exactly like a non-virtual base class.

The “virtualness” of W is a property of the derivation specified by AW and BW and not a property of W itself. Every virtual base in an inheritance DAG refers to the same object.”

Or graphically:

Image

What might W, AW, BW, and CW be in real programs? My original example was a simple windows system, based on ideas from the Lisp literature:

Image

This, in my experience, is a bit contrived, but was based on a real example and, most importantly for presentation, it was intuitive. Several examples can be found in the standard iostreams library [Shopiro,1989]:

Image

I saw no reason for virtual base classes being more useful or more fundamental than ordinary base classes or vice versa, so I decided to support both. I chose ordinary bases as the default because their implementation is cheaper in run time and space than virtual bases and because "“programming using virtual bases is a bit trickier than programming using non-virtual bases. The problem is to avoid multiple calls of a function in a virtual class when that is not desired” [2nd]; see also §12.5.

Because of implementation difficulties, I was tempted not to include the notion of virtual bases in the language. However, I considered the argument that there had to be a way to represent dependencies between sibling classes conclusive. Sibling classes can communicate only through a common root class, through global data, or through explicit pointers. If there were no virtual base classes, the need for a common root would lead to overuse of “universal” base classes. The mixin style described in §12.3.1 is an example of such “sibling communication.”

If multiple inheritance should be supported, some such facility had to be included. On the other hand, I consider the simple and unexciting applications of multiple inheritance, such as defining one class with the sum of the attributes of two otherwise independent classes, by far the most useful.

12.3.1 Virtual Bases and Virtual Functions

The combination of abstract classes and virtual base classes was intended to support a style of programming roughly corresponding to the mixin style used in some Lisp systems. That is, an abstract base class defines an interface, and several derived classes contribute to the implementation. Each derived class (each mixin) contributes something to the complete class (mix). The origin of the term mixin is reliably reported to be the addition of nuts, raisins, gummy bears, cookies, etc., to ice cream in an ice cream shop somewhere near MIT.

To enable this style, two rules are necessary:

[1] It must be possible to override virtual functions of a base class from different derived classes; otherwise, the essential parts of an implementation must come from a single inheritance chain as in the slist_set example in §13.2.2.

[2] It must be possible to determine which function is the one overriding a virtual function and to catch inconsistent overriding in an inheritance lattice; otherwise we would have to rely on order dependencies or run-time resolution.

Consider the example above. Say W had virtual functions f() and g():

class W {
    // ...
    virtual void f();
    virtual void g();
};

and AW and BW each overrode one of those:

class AW : public virtual W {
     // ...
    void g();
};

class BW : public virtual W {
    // ...
    void f();
};

class CW : public AW, public BW, public virtual W {
    // ...
};

Then a CW can be used like this:

CW* pew = new CW;
AW* paw = pew;
BW* pbw = pew;

void fff()
{
    pcw->f();   // invokes BW::f()
    pcw->g();   // invokes AW::g()

    paw->f();   // invokes BW::f() !
    pbw->g();   // invokes AW::g() !
}

As ever for virtual functions, the same function is called independently of the type of pointer used for the object. The importance of this is exactly that it allows different classes to add to a common base class and benefit from each other’s contributions. Naturally, the derived classes have to be designed with this in mind and can sometimes be composed only with care and some knowledge of sibling classes.

Allowing overriding from different branches requires a rule for what is acceptable and for what combinations of overriding are to be rejected as errors. The same function must be invoked by a virtual function call independently of how the class object is specified. Andrew Koenig and I discovered what we consider the only rule that ensures that [ARM]:

"A name B::f dominates a name A::f if its class B has A as a base. If a name dominates another, no ambiguity exists between the two; the dominant name is used when there is a choice. For example,

class V { public: int f(); int x; };
class B : public virtual V { public: int f() ; int x; };
class C : public virtual V { };


class D : public B, public C { void g(); };


void D:: g()
{
    x++;   // ok: B::x dominates V::x
    f();   // ok: B::f() dominates V::f()
}

Or graphically,

Image

Note that dominance applies to names and not just to functions.

The dominance rule is necessary for virtual functions – since it is the rule for which function should be invoked for a virtual call – and experience showed it applying nicely to non-virtual functions as well. Early use of a compiler that did not apply the dominance rule to non-virtual functions led to programmer errors and contorted programs.”

From an implementer’s point of view, the dominance rule is the usual lookup rule applied to determine whether there is a unique function that can be put in the virtual function table. A laxer rule doesn’t ensure this, and a stricter rule would disallow reasonable calls.

The rules for abstract classes and the dominance rule ensure that objects can be created only for classes that provide a complete and consistent set of services. Without these rules, a programmer would have little hope of avoiding serious run-time errors when using nontrivial frameworks.

12.4 The Object Layout Model

Multiple inheritance complicates the object model in two ways:

[1] An object can have more than one virtual function table.

[2] A virtual function table must provide a way of finding the sub-object corresponding to the class that supplied the virtual function.

Consider:

class A {
public:
    virtual void f(int);
};

class B {
public :
    virtual void f(int);
    virtual void g();
};

class C : public A , public B {
public:
    void f(int);
};

An object of class C might look like this:

Image

The two vtbls are necessary because you can have objects of classes A and B as well as As and Bs that are parts of a C. When you get a pointer to a B, you must be able to invoke a virtual function without knowing whether that B is a "plain B,” a “B part” of a C, or some third kind of object containing a B. Thus, every B needs a vtbl that is accessed in the same way in all cases.

The delta is necessary because once the vtbl is found, the function invoked must be invoked for the sub-object for which it was defined. For example, calling g() for a C object requires a this pointer pointing to the B sub-object of the C, while a call of f() for a C object requires a this pointer pointing to the complete C object.

Given the layout suggested above, a call

void ff(B* pb)
{
    pb->f(2);
}

can be implemented by code like this:

/* generated code */
vtbl_entry* vt = &pb->vtbl[index(f)];
(*vt->fct)((B*)((char*)pb+vt->delta),2);

This is the implementation strategy I followed when I first implemented multiple inheritance in Cfront. It has the virtue of being easily expressed in C and is therefore portable. The generated code also has the advantage of containing no branching, so it is fast on heavily pipelined machine architectures.

An alternative implementation avoids storing the delta for the this pointer in the virtual function table. Instead, a pointer to code to be executed is stored. When no adjustment to this is needed, the pointer in the vtbl points to the instance of the virtual function to be executed; when this must be adjusted, the pointer in the vtbl points to code that adjusts the pointer then executes the appropriate instance of the virtual function. The class C, declared above, would be represented in this scheme as follows:

Image

This scheme allows more compact virtual function tables. It also gives faster calls to virtual functions where the delta is 0. Note that the delta is 0 in all single inheritance cases. The change of control after the modification with the delta can be costly on heavily pipelined machines, but these kinds of costs are very architecture-dependent, so no general guidelines can be given. The drawback of such a scheme is that it is less portable. For example, not all machine architectures allow a jump into the body of another function.

The code that adjusts the this pointer is usually called a thunk. This name goes back at least as far as the early implementations of Algol160 where such small pieces of code were used to implement call-by-name.

I knew these two implementation strategies for virtual functions at the time I designed multiple inheritance and first implemented it. From a language-design point of view they are almost equivalent, but the thunk implementation has the desirable property that no cost in time or space is incurred for C++ programs using single inheritance – thus fulfilling the zero-overhead “design rule” exactly (§4.5). Both implementation strategies provide acceptable performance in the cases I have looked at.

12.4.1 Virtual Base Layout

Why was a “virtual base class” called virtual? Often, I just give the flip explanation “well, virtual means magic,” and carry on with some more urgent issue, but there is a better explanation. That explanation emerged in discussions with Doug McIlroy long before the first public presentation of multiple inheritance in C++. A virtual function is a function that you find through an indirection through an object. Similarly, the object representing a virtual base class isn’t in a fixed position in all classes derived from it and must thus also be accessed through an indirection. Also, a base class is defined as an unnamed member. Consequently, had virtual data members been allowed, a virtual base class would have been an example of a virtual data member. I wish I had implemented virtual bases in the way suggested by this explanation. For example, given a class X with a virtual base V and a virtual function f we’d have:

Image

rather than the “optimized” implementation I used in Cfront:

Image

What is called &X::Vobj in these figures is the offset of the object representing the virtual base V in X. The former model is cleaner and more general. It wastes a miniscule amount of run time compared to the “optimized” model while saving some space.

Virtual data members is one of the extensions that people keep proposing for C++.

Typically, a proposer wants only “static virtual data,” “constant virtual data,” or even “constant static virtual data,” rather than a more general concept. However, it has been my experience that the proposer typically has a single application in mind: run-time identification of an object’s type. There are other ways of getting that; see §14.2.

12.4.2 Virtual Bases and Casting

Sometimes, people express surprise that being a virtual base class is a property of the derivation rather than a property of the base class itself. However, the object layout model described above doesn’t provide sufficient information to find a derived class given only a pointer to one of its virtual bases; there is no “back pointer” to the enclosing objects. For example:

class A : public virtual complex { /* ... */ };
class B : public virtual complex { /* ... */ };
class C : public A, public B { /* ... */ };

void f(complex* p1, complex* p2, complex* p3
{
    (A*)p1; // error: can't cast from virtual base
    (A*)p2; // error: can't cast from virtual base
    (A*)p3; // error: can't cast from virtual base
}

Given a call:

void g()
{
    f(new A, new B, new C);
}

the complex pointed to by p1 is unlikely to be in the same position relative to the A as it is in the complex pointed to by p2. Consequently, a cast from a virtual base to a derived class requires a run-time action based on information stored in the base class object. Such information cannot be available in objects of simple classes under layout constraints.

Had I viewed casts with less suspicion, I would have viewed the lack of casting from virtual bases as more serious. Anyway, classes accessed through virtual functions and classes that simply hold a few data items often make the best virtual bases. If a base has only data members, you shouldn’t pass a pointer to it around as a representative of the complete class. If, on the other hand, a base has virtual functions you can call those functions. In either case, casting ought to be unnecessary. Also, if you really must cast from a base to its derived class, then dynamic_cast14.2.2) solves this problem for bases with virtual functions.

Ordinary bases are in a fixed position in every object of a given derived class and that position is known to the compiler. Therefore, a pointer to an ordinary base class can be cast to a pointer to a derived class without special problems or overhead.

Had it been required that a class should be explicitly declared as a “potential virtual base,” special rules could have applied to virtual bases. For example, information could have been added to allow casting from a “virtual base” to a class derived from it. My reason for not making a “virtual base class” a special kind of class was that this would have forced programmers to define two different versions of a concept: one for ordinary class use and another for virtual base use.

Alternatively, we could add the overhead necessary for fully general virtual class use to every class object. However, that would impose significant overhead on applications that don’t use virtual bases and would cause layout compatibility problems.

Consequently, I allowed any class to be used as a virtual base and accepted the ban on casting as a restriction on the use of virtual bases.

12.5 Method Combination

It is quite common to synthesize a derived class function from base class versions of the same function. This is often called method combination and is supported directly in some object-oriented languages, but – except for constructors, destructors, and copy operations – not in C++. Maybe I should have revived the notion of call and return functions (§2.11.3) to mimic the CLOS :before and :after methods. However, people were already worrying about the complexity of the multiple inheritance mechanisms, and I am always reluctant to re-open old wounds.

Instead, I observed that method combination could be achieved manually. The problem is to avoid multiple calls of a function in a virtual class when that is not desired. Here is a possible style:

class W {
    // ...
protected:
    void _f() { /* W's own stuff */ }
    // ...
public:
    void f() { _f(); }
    // ...
};

Each class provides a protected function _f() doing “the class’ own stuff” for use by derived classes. It also provides a public function f() as the interface for use by the “general public.” A derived class’s f() does its “own stuff” by calling _f() and its base classes’ “own stuff” by calling their _f()s:

class A : public virtual W {
    // ...
protected:
    void _f() { /* A's own stuff */ }
    // ...

public:
    void f() { _f(); W::_f(); }
    // ...
};

class B : public virtual W {
    // ...
protected:
    void _f () { /* B's own stuff */ }
    // ...
public:
    void f() { _f(); W::_f(); }
    // ...
};

In particular, this style enables a class that is (indirectly) derived twice from a class W to call W::f() only once:

class C : public A, public B, public virtual W {
    // ...
protected:
    void _f() { /* C's own stuff */ }
    // ...
public:
    void f() { _f(); A::_f(); B::_f(); W::_f(); }
    // ...
};

This is less convenient than automatically generated composite functions, but in some ways it is more flexible.

12.6 The Multiple Inheritance Controversy

Multiple inheritance in C++ became controversial [Cargill, 1991] [Carroll, 1991] [Waldo,1991] [Sakkinen,1992] [Waldo,1993] for several reasons. The arguments against it centered around the real and imaginary complexity of the concept, its utility, and the impact of multiple inheritance on other language features and on tool building:

[1] Multiple inheritance was seen as the first major extension to C++. Some C++ old-timers saw it as an unnecessary frill, a complication, and possibly as the wedge that would open the door to an avalanche of new features into C++. For example, at the very first C++ conference in Santa Fe (§7.1.2) Tom Cargill got loud applause for the amusing, but not very realistic, suggestion that anyone who proposed a new feature for C++ should also propose an old feature of similar complexity to be removed. I approve of the sentiment, but cannot draw the conclusion that C++ would be better without multiple inheritance or that C++ as of 1985 is better than its larger 1993 incarnation. Jim Waldo later followed up Tom’s suggestion with a further idea: Proposers of new features should be required to donate a kidney. That would – Jim pointed out – make people think hard before proposing, and even people without any sense would propose at most two extensions. I note that not everyone is as keen on new features as one might think from reading journals, reading netnews, and listening to questions after talks.

[2] I implemented multiple inheritance in a way that imposed an overhead even if a user didn’t use anything but single inheritance. This violated the “you don’t pay for what you don’t use” rule (§4.5) and led to the (false) impression that multiple inheritance is inherently inefficient. I considered the overhead acceptable because it was small (one array access plus one addition per virtual function call), and because I knew a simple technique for implementing multiple inheritance so that there is absolutely no change in the implementation of a virtual function call in a single inheritance hierarchy (§12.4). I chose the “sub-optimal” implementation because it was more portable.

[3] Smalltalk doesn’t support multiple inheritance, and a number of people equate object-oriented programming with both “good” and Smalltalk. Such people often surmise that “if Smalltalk doesn’t have it, multiple inheritance must be either bad or unnecessary.” Naturally, this doesn’t follow. Maybe Smalltalk would benefit from multiple inheritance – and maybe it wouldn’t; that is not the issue. However, it was clear to me that several of the techniques that Smalltalk aficionados recommended as alternatives to multiple inheritance didn’t apply to a statically typed language such as C++. Language wars are typically silly; the ones that center on single features in isolation are even more silly. Attacks on multiple inheritance that are really misdirected attacks on static type checking or disguised defenses against imagined attacks on Smalltalk are best ignored.

[4] My presentation of multiple inheritance [Stroustrup,1987] was very technical and focused on implementation issues at the expense of explanations of programming techniques using it. This led many people to the conclusion that multiple inheritance had few uses and was horrendously hard to implement. I suspect that if I had presented single inheritance in the same manner, they would have drawn exactly the same conclusions about it.

[5] Some consider multiple inheritance fundamentally bad because “it is too hard to use and thus leads to poor design and buggy code.” Multiple inheritance can certainly be overused, but so can every interesting language feature. What matters more to me is that I have seen real programs in which use of multiple inheritance has yielded a structure that the programmers considered superior to the single inheritance alternatives and where I didn’t see any obvious alternatives that would simplify the structure of the program or its maintenance. I suspect that some of the claims that multiple inheritance is error-prone are based exclusively on experience with languages that don’t provide C++’s level of compile-time error detection.

[6] Others consider multiple inheritance too weak a mechanism and sometimes point to delegation as an alternative. Delegation is a mechanism to forward operations to another object at run time [Stroustrup,1987]. I liked the idea of delegation, implemented a variant of it for C++, and tried it out. The results were unanimous and discouraging: Every user encountered serious problems due to flaws in their delegation-based designs (§12.7).

[7] It has also been claimed that multiple inheritance itself is acceptable, but having it in C++ leads to difficulties with potential features in the language (such as garbage collection) and makes it unnecessarily hard to build tools (such as database systems) for C++. Certainly, multiple inheritance complicates tool building. Only time will tell if the increase in complexity of tools outweighs the benefits of having multiple inheritance available for application design and implementation.

[8] Finally, it has been argued (mostly years after its introduction into C++) that multiple inheritance itself is a good idea, but that C++’s version of the idea is wrong. Such arguments may be of interest to the designers of “C++++,” but I don’t find the suggestions very helpful in my work to improve C++, its related tools, and programming techniques. People rarely provide practical evidence to support their suggested improvements, the suggestions are rarely detailed, the various suggested improvements differ radically, and the problems of a transition from the current rules are rarely considered.

I think – as I did then – that the fundamental flaw in these arguments is that they take multiple inheritance far too seriously. Multiple inheritance doesn’t solve all of your problems, but it doesn’t need to because it is quite cheap. Sometimes multiple inheritance is very convenient to have. Grady Booch [Booch,1991] expresses a slightly stronger sentiment: “Multiple inheritance is like a parachute; you don’t need it very often, but when you do it is essential.” His opinion is partially based on the experience gained in the reimplementation of the Booch components from Ada into C++ (see §8.4.1). This library of container classes and associated operations, implemented by Grady Booch and Mike Vilot, is one of the better examples of the use of multiple inheritance [Booch,1990] [Booch, 1993b].

I have kept out of the multiple inheritance debates: multiple inheritance is in C++ and cannot be taken out or radically changed; I personally find multiple inheritance useful at times; some people insist that multiple inheritance is essential to their approach to design and implementation; it is still too early to have solid data and experience about the value of C++ multiple inheritance in large scale use; and, finally, I don’t like to spend my time on sterile discussions.

As far as I can judge, most successful uses of multiple inheritance have followed a few simple patterns:

[1] Merging of independent or almost-independent hierarchies; task and display is an example of this (§12.2).

[2] Composition of interfaces; stream I/O is an example of this (§12.3).

[3] Composing a class out of an interface and an implementation; slist_set is an example of this (§13.2.2).

More examples of multiple inheritance can be found in §13.2.2, §14.2.7, and §16.4.

Most failures have occurred when someone tried to force an alien style onto C++. In particular, a direct transcription of a CLOS design relying on linearization for ambiguity resolution, matching names for sharing within the hierarchy, and :before and :after methods for creating composite operations tends to get very unpleasant and complicated for large programs.

12.7 Delegation

The original multiple inheritance design as presented to the European UNIX Users’ Group (EUUG) conference in Helsinki in May 1987 [Stroustrup,1987] contained a notion of delegation [Agha,1986].

A user was allowed to specify a pointer to some class among the base classes in a class declaration. The object thus designated would be used exactly as if it was an object representing a base class. For example:

class B { int b; void f(); };
class C : *p { B* p; int c; };

The :*p meant that the object pointed to by p would be used exactly as if it represented a base class of C:

void f(C* q)
{
    q->f();     // meaning q->p->f()
}

An object of class C looked something like this after C::p has been initialized:

Image

This concept looked very promising for representing structures that require more flexibility than is provided by ordinary inheritance. In particular, assignment to a delegation pointer could be used to reconfigure an object at run time. The implementation was trivial and the run-time and space efficiency ideal. Consequently, I tried out an implementation on a few users. In particular, Bill Hopkins contributed significant experience and effort to this issue. Unfortunately, every user of this delegation mechanism suffered serious bugs and confusion. Because of this, the delegation was removed from the design and from the Cfront that was shipped as Release 2.0.

Two problems appeared to be the cause of bugs and confusion:

[1] Functions in the delegating class do not override functions of the class delegated to.

[2] The function delegated to cannot use functions from the delegating class or in other ways “get back” to the delegating object.

Naturally, the two problems are related. Equally naturally, I had considered these potential problems and warned users about them. The warnings didn’t help – I even forgot my own rules and got caught. Thus the problems didn’t seem to belong to the category of minor blemishes that can be handled through a combination of education and compiler warnings. At the time, the problems seemed insurmountable. Even if I had come up with a better idea, I did not have the time to repeat my experiments with a revised concept and implementation.

In retrospect, I think the problems are fundamental. Solving the problem [1] would require the virtual function table of the object delegated to be changed when it is bound to a delegating object. This seems out of line with the rest of the language and very difficult to define sensibly. We also found examples where we wanted to have two objects delegate to the same “shared” object. Similarly, we found examples where we needed to delegate through a B* to an object of a derived class D.

Because delegation isn’t supported directly in C++, we must use a workaround if we really need it. Often, the solution to a problem requiring delegation involves a smart pointer (§11.5.1). Alternatively, the delegating class provides a complete interface, and then “manually” forwards the requests to some other object (§11.5.2).

12.8 Renaming

During late 1989 and early 1990 several people discovered a problem arising from name clashes in multiple inheritance hierarchies [ARM]:

“Merging two class hierarchies by using them as base classes for a common derived class can cause a practical problem where the same name is used in both hierarchies, but where it refers to different operations in the different hierarchies. For example,

class Lottery {
    // ...
    virtual int draw();
};

class GraphicalObject {
    // ...
    virtual void draw();
};

class LotterySimulation
    : public Lottery , public GraphicalObject {
    // ...
};

In LotterySimulation we would like to override both Lottery::draw() and GraphicalObject::draw(), but with two distinct functions, since draw () has completely different meanings in the two base classes. We would also like LotterySimulation to have distinct, unambiguous names for the inherited functions Lottery::draw() and GraphicalObject::draw(). This feature came within an inch of becoming the first non-mandated extension to be accepted for C++.

The semantics of this concept are simple, and the implementation is trivial; the problem seems to be to find a suitable syntax. The following has been suggested:

class LotterySimulation
    : public Lottery , public GraphicalObject {
    // ...
    virtual int l_draw() = Lottery::draw;
    virtual void go_draw() = GraphicalObject::draw;
};

This would extend the pure virtual syntax in a natural manner.”

After some discussion on the extensions working group mail reflector and a few position papers by Martin O’Riordan and by me, the proposal was presented at the standards meeting in Seattle in July 1990. There appeared to be a massive majority for making this the first non-mandated extension to C++. At that point, Beth Crockett from Apple stopped the committee dead in its tracks by invoking what is known as the “two week rule:” Any member can postpone voting on a proposal that has not been in the hands of the members at least two weeks before the meeting until the following meeting. This rule protects people against being rushed into things they don’t understand and ensures that there will always be time to consult with colleagues.

As you might imagine, Beth didn’t gain instant popularity by that veto. However, her caution was well founded, and she saved us from making a bad mistake. Thanks! As we reexamined the problem after the meeting, Doug McIlroy observed that, contrary to our expectations, this problem does have a solution within C++ [ARM]:

“Renaming can be achieved through the introduction of an extra class for each class with a virtual function that needs to be overridden by a function with a different name plus a forwarding function for each such function. For example:

class LLottery : public Lottery {
    virtual int l_draw() = 0;
    int draw() { return l_draw(); } // overrides
};

class GGraphicalObject : public GraphicalObject {
    virtual void go_draw() = 0;
    void draw() { go_draw(); } // overrides
};

class LotterySimulation
    : public LLottery , public GGraphicalObject {
    // ...
    int l_draw(); // overrides
    void go_draw(); // overrides
};

Consequently, a language extension to express renaming is not necessary and is only worthwhile if the need to resolve such name clashes proves common.”

At the next meeting, I presented this technique. During the discussion that followed, we agreed that such name clashes were unlikely to be common enough to warrant a separate language feature. I also observed that merging large class hierarchies is not likely to become everyday work for novices. The experts who most likely will be doing such merging can apply a workaround as easily as a more elegant language feature.

A further – and more general – objection to renaming is that I dislike chasing chains of aliases while maintaining code. If the name I see spelled f is really the g defined in the header that actually is described as h in the documentation and what is called k in your code, then we have a problem. Naturally, this would be an extreme case, but not out of line with examples created by macro-aficionados. Every renaming requires understanding a mapping by both users and tools.

Synonyms can be useful and occasionally essential. However, their use should be minimized to maintain clarity and commonality of code used in different contexts. Further features directly supporting renaming would simply encourage (mis)use of synonyms. This argument resurfaced as a reason for not providing general renaming features in connection with namespaces (§17).

12.9 Base and Member Initializers

When multiple inheritance was introduced, the syntax for initializing base classes and members had to be extended. For example:

class X : public A, public B {
    int xx;
    X(int a, int b)
        : A(a), // initialize base A
          B(b), // initialize base B
          xx(1) // initialize member xx
    {)
};

This initialization syntax is a direct parallel to the syntax for initializing class objects:

A x(l) ;
B y(2) ;

At the same time, the order of initialization was defined to be the order of declaration. Leaving the initialization order unspecified in the original definition of C++ gave an unnecessary degree of freedom to language implementers at the expense of the users.

In most cases, the order of initialization of members doesn’t matter, and in most cases where it does matter, the order dependency is an indication of bad design. In a few cases, however, the programmer absolutely needs control of the order of initialization. For example, consider transmitting objects between machines. An object must be reconstructed by a receiver in exactly the reverse order in which it was decomposed for transmission by a sender. This cannot be guaranteed for objects communicated between programs compiled by compilers from different suppliers unless the language specifies the order of construction. I remember Keith Gorlen, of NIH library fame (§7.1.2), pointing this out to me.

The original definition of C++ neither required nor allowed a base class to be named in a base class initializer. For example, given a class vector:

class vector {
    // ...
    vector(int);
};

we may derive another class vec:

class vec : public vector {
    // ...
    vec(int,int);
};

The vec constructor must invoke the vector constructor. For example:

vec::vec(int low, int high)
    : (high-low-1) // argument for base class constructor
{
    // ...
}

This notation caused much confusion over the years.

Using the base class name explicitly as required by 2.0 makes it reasonably clear even to novices what is going on:

vec::vec(int low, int high) : vector(high-low-1)
{
    // ...
}

I now consider the original syntax as a classic case of a notation being logical, minimal, and too terse. The problems that occurred in teaching base class initialization completely vanished with the new syntax.

The old-style base class initializer was retained for a transition period. It could be used only in the single inheritance case since it is ambiguous otherwise.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.45.162