Class Hierarchies - Abstraction Mechanisms - The C++ Programming Language (2013)

The C++ Programming Language (2013)

Part III: Abstraction Mechanisms

21. Class Hierarchies

Abstraction is selective ignorance.

– Andrew Koenig

Introduction

Design of Class Hierarchies

Implementation Inheritance; Interface Inheritance; Alternative Implementations; Localizing Object Creation

Multiple Inheritance

Multiple Interfaces; Multiple Implementation Classes; Ambiguity Resolution; Repeated Use of a Base Class; Virtual Base Classes; Replicated vs. Virtual Bases

Advice

21.1. Introduction

The primary focus of this chapter is design techniques, rather than language features. The examples are taken from user-interface design, but I avoid the topic of event-driven programming as commonly used for graphical user interface (GUI) systems. A discussion of exactly how an action on the screen is transformed into a call of a member function would add little to the issues of class hierarchy design and has a huge potential for distraction: it is an interesting and important topic in its own right. For an understanding of GUI, have a look at one of the many C++ GUI libraries.

21.2. Design of Class Hierarchies

Consider a simple design problem: Provide a way for a program (“an application”) to get an integer value from a user. This can be done in a bewildering number of ways. To insulate our program from this variety, and also to get a chance to explore the possible design choices, let us start by defining our program’s model of this simple input operation.

The idea is to have a class Ival_box (“integer value input box”) that knows what range of input values it will accept. A program can ask an Ival_box for its value and ask it to prompt the user if necessary. In addition, a program can ask an Ival_box if a user changed the value since the program last looked at it:

Image

Because there are many ways of implementing this basic idea, we must assume that there will be many different kinds of Ival_boxes, such as sliders, plain boxes in which a user can type a number, dials, and voice interaction.

The general approach is to build a “virtual user-interface system” for the application to use. This system provides some of the services provided by existing user-interface systems. It can be implemented on a wide variety of systems to ensure the portability of application code. Naturally, there are other ways of insulating an application from a user-interface system. I chose this approach because it is general, because it allows me to demonstrate a variety of techniques and design tradeoffs, because those techniques are also the ones used to build “real” user-interface systems, and – most important – because these techniques are applicable to problems far beyond the narrow domain of interface systems.

In addition to ignoring the topic of how to map user actions (events) to library calls, I also ignore the need for locking in a multi-threaded GUI system.

21.2.1. Implementation Inheritance

Our first solution is a class hierarchy using implementation inheritance (as is commonly found in older programs).

Class Ival_box defines the basic interface to all Ival_boxes and specifies a default implementation that more specific kinds of Ival_boxes can override with their own versions. In addition, we declare the data needed to implement the basic notion:

class Ival_box {
protected:
int val;
int low, high;
bool changed {false}; //
changed by user using set_value()
public:
Ival_box(int ll, int hh) :val{ll}, low{ll}, high{hh} { }

virtual int get_value() { changed = false; return val; } // for application
virtual void set_value(int i) { changed = true; val = i; } // for user
virtual void reset_value(int i) { changed = false; val = i; } // for application
virtual void prompt() { }
virtual bool was_changed() const { return changed; }

virtual ~Ival_box() {};
};

The default implementation of the functions is pretty sloppy and is provided here primarily to illustrate the intended semantics. A realistic class would, for example, provide some range checking.

A programmer might use these “ival classes” like this:

void interact(Ival_box* pb)
{
pb–>prompt(); //
alert user
// ...
int i = pb–>get_value();
if (pb–>was_changed()) {
//
... new value; do something ...
}
else {
//
... do something else ...
}
}

void some_fct()
{
unique_ptr<Ival_box> p1 {new Ival_slider{0,5}}; //
Ival_slider derived from Ival_box
interact(p1.get());

unique_ptr<Ival_box> p2 {new Ival_dial{1,12}};
interact(p2.get());
}

Most application code is written in terms of (pointers to) plain Ival_boxes the way interact() is. That way, the application doesn’t have to know about the potentially large number of variants of the Ival_box concept. The knowledge of such specialized classes is isolated in the relatively few functions that create such objects. This isolates users from changes in the implementations of the derived classes. Most code can be oblivious to the fact that there are different kinds of Ival_boxes.

I use unique_ptr5.2.1, §34.3.1) to avoid forgetting to delete the ival_boxes.

To simplify the discussion, I do not address issues of how a program waits for input. Maybe the program really does wait for the user in get_value() (e.g., using a get() on a future; §5.3.5.1), maybe the program associates the Ival_box with an event and prepares to respond to a callback, or maybe the program spawns a thread for the Ival_box and later inquires about the state of that thread. Such decisions are crucial in the design of user-interface systems. However, discussing them here in any realistic detail would simply distract from the presentation of programming techniques and language facilities. The design techniques described here and the language facilities that support them are not specific to user interfaces. They apply to a far greater range of problems.

The different kinds of Ival_boxes are defined as classes derived from Ival_box. For example:

class Ival_slider : public Ival_box {
private:
//
... graphics stuff to define what the slider looks like, etc. ...
public:
Ival_slider(int, int);
int get_value() override; //
get value from user and deposit it in val
void prompt() override;
};

The data members of Ival_box were declared protected to allow access from derived classes. Thus, Ival_slider::get_value() can deposit a value in Ival_box::val. A protected member is accessible from a class’s own members and from members of derived classes, but not to general users (see §20.5).

In addition to Ival_slider, we would define other variants of the Ival_box concept. These could include Ival_dial, which lets you select a value by turning a knob; Flashing_ival_slider, which flashes when you ask it to prompt(); and Popup_ival_slider, which responds to prompt() by appearing in some prominent place, thus making it hard for the user to ignore.

From where would we get the graphics stuff? Most user-interface systems provide a class defining the basic properties of being an entity on the screen. So, if we use the system from “Big Bucks Inc.,” we would have to make each of our Ival_slider, Ival_dial, etc., classes a kind ofBBwidget. This would most simply be achieved by rewriting our Ival_box so that it derives from BBwidget. In that way, all our classes inherit all the properties of a BBwidget. For example, every Ival_box can be placed on the screen, obey the graphical style rules, be resized, be dragged around, etc., according to the standard set by the BBwidget system. Our class hierarchy would look like this:

class Ival_box : public BBwidget { /* ... */ }; // rewritten to use BBwidget
class Ival_slider : public Ival_box { /* ... */ };
class Ival_dial : public Ival_box { /*
... */ };
class Flashing_ival_slider : public Ival_slider { /*
... */ };
class Popup_ival_slider : public Ival_slider { /*
... */ };

or graphically:

Image

21.2.1.1. Critique

This design works well in many ways, and for many problems this kind of hierarchy is a good solution. However, there are some awkward details that could lead us to look for alternative designs.

We retrofitted BBwidget as the base of Ival_box. This is not quite right (even if this style is common in real-world systems). The use of BBwidget isn’t part of our basic notion of an Ival_box; it is an implementation detail. Deriving Ival_box from BBwidget elevated an implementation detail to a first-level design decision. That can be right. For example, using the environment defined by “Big Bucks Inc.” may be a key decision based on how our organization conducts its business. However, what if we also wanted to have implementations of our Ival_boxes for systems from “Imperial Bananas,” “Liberated Software,” and “Compiler Whizzes”? We would have to maintain four distinct versions of our program:

class Ival_box : public BBwidget { /* ... */ }; // BB version
class Ival_box : public CWwidget { /* ... */ }; // CW version
class Ival_box : public IBwidget { /* ... */ }; // IB version
class Ival_box : public LSwindow { /* ... */ }; // LS version

Having many versions could result in a version control nightmare.

In reality, we are unlikely to find a simple, coherent, two-letter prefix scheme. More likely, the libraries from different purveyors would be in different namespaces and use different terminologies for similar concepts, such as BigBucks::Widget, Wizzies::control, and LS::window. But that does not affect our class hierarchy design discussion, so to simplify I ignore naming and namespace issues.

Another problem is that every derived class shares the basic data declared in Ival_box. That data is, of course, an implementation detail that also crept into our Ival_box interface. From a practical point of view, it is also the wrong data in many cases. For example, an Ival_slider doesn’t need the value stored specifically. It can easily be calculated from the position of the slider when someone executes get_value(). In general, keeping two related, but different, sets of data is asking for trouble. Sooner or later someone will get them out of sync. Also, experience shows that novice programmers tend to mess with protected data in ways that are unnecessary and that cause maintenance problems. Data members are better kept private so that writers of derived classes cannot mess with them. Better still, data should be in the derived classes, where it can be defined to match requirements exactly and cannot complicate the life of unrelated derived classes. In almost all cases, a protected interface should contain only functions, types, and constants.

Deriving from BBwidget gives the benefit of making the facilities provided by BBwidget available to users of Ival_box. Unfortunately, it also means that changes to class BBwidget may force users to recompile or even rewrite their code to recover from such changes. In particular, the way most C++ implementations work implies that a change in the size of a base class requires a recompilation of all derived classes.

Finally, our program may have to run in a mixed environment in which windows of different user-interface systems coexist. This could happen either because two systems somehow share a screen or because our program needs to communicate with users on different systems. Having our user-interface systems “wired in” as the one and only base of our one and only Ival_box interface just isn’t flexible enough to handle those situations.

21.2.2. Interface Inheritance

So, let’s start again and build a new class hierarchy that solves the problems presented in the critique of the traditional hierarchy:

[1] The user-interface system should be an implementation detail that is hidden from users who don’t want to know about it.

[2] The Ival_box class should contain no data.

[3] No recompilation of code using the Ival_box family of classes should be required after a change of the user-interface system.

[4] Ival_boxes for different interface systems should be able to coexist in our program.

Several alternative approaches can be taken to achieve this. Here, I present one that maps cleanly into the C++ language.

First, I specify class Ival_box as a pure interface:

class Ival_box {
public:
virtual int get_value() = 0;
virtual void set_value(int i) = 0;
virtual void reset_value(int i) = 0;
virtual void prompt() = 0;
virtual bool was_changed() const = 0;
virtual ~Ival_box() { }
};

This is much cleaner than the original declaration of Ival_box. The data is gone and so are the simplistic implementations of the member functions. Gone, too, is the constructor, since there is no data for it to initialize. Instead, I added a virtual destructor to ensure proper cleanup of the data that will be defined in the derived classes.

The definition of Ival_slider might look like this:

class Ival_slider : public Ival_box, protected BBwidget {
public:
Ival_slider(int,int);
~Ival_slider() override;

int get_value() override;
void set_value(int i) override;
//
...
protected:
// ... functions overriding BBwidget virtual functions
// e.g., BBwidget::draw(), BBwidget::mouse1hit() ...
private:
// ... data needed for slider ...
};

The derived class Ival_slider inherits from an abstract class (Ival_box) that requires it to implement the base class’s pure virtual functions. It also inherits from BBwidget which provides it with the means of doing so. Since Ival_box provides the interface for the derived class, it is derived using public. Since BBwidget is only an implementation aid, it is derived using protected20.5.2). This implies that a programmer using Ival_slider cannot directly use facilities defined by BBwidget. The interface provided by Ival_slider is the one inherited from Ival_box, plus whatIval_slider explicitly declares. I used protected derivation instead of the more restrictive (and usually safer) private derivation to make BBwidget available to classes derived from Ival_slider. I used explicit override because this “widget hierarchy” is exactly the kind of large, complicated hierachy where being explicit can help minimize confusion.

Deriving directly from more than one class is usually called multiple inheritance21.3). Note that Ival_slider must override functions from both Ival_box and BBwidget. Therefore, it must be derived directly or indirectly from both. As shown in §21.2.1.1, deriving Ival_slider indirectly from BBwidget by making BBwidget a base of Ival_box is possible, but doing so has undesirable side effects. Similarly, making the “implementation class” BBwidget a member of Ival_box is not a solution because a class cannot override virtual functions of its members. Representing the window by a BBwidget* member in Ival_box leads to a completely different design with a separate set of tradeoffs.

To some people, the words “multiple inheritance” indicate something complicated and scary. However, the use of one base class for implementation details and another for interface (the abstract class) is common to all languages supporting inheritance and compile-time checked interfaces. In particular, the use of the abstract class Ival_box is almost identical to the use of an interface in Java or C#.

Interestingly, this declaration of Ival_slider allows application code to be written exactly as before. All we have done is to restructure the implementation details in a more logical way.

Many classes require some form of cleanup for an object before it goes away. Since the abstract class Ival_box cannot know if a derived class requires such cleanup, it must assume that it does require some. We ensure proper cleanup by defining a virtual destructor Ival_box::~Ival_box()in the base and overriding it suitably in derived classes. For example:

void f(Ival_box* p)
{
//
...
delete p;
}

The delete operator explicitly destroys the object pointed to by p. We have no way of knowing exactly to which class the object pointed to by p belongs, but thanks to Ival_box’s virtual destructor, proper cleanup as (optionally) defined by that class’ destructor will be done.

The Ival_box hierarchy can now be defined like this:

class Ival_box { /* ... */ };
class Ival_slider
: public Ival_box, protected BBwidget { /*
... */ };
class Ival_dial
: public Ival_box, protected BBwidget { /*
... */ };
class Flashing_ival_slider
: public Ival_slider { /*
... */ };
class Popup_ival_slider
: public Ival_slider { /*
... */ };

or graphically:

Image

I used a dashed line to represent protected inheritance (§20.5.1). General users cannot access the protected bases because they are (correctly) considered part of the implementation.

21.2.3. Alternative Implementations

This design is cleaner and more easily maintainable than the traditional one – and no less efficient. However, it still fails to solve the version control problem:

class Ival_box { /* ... */ }; // common
class Ival_slider
: public Ival_box, protected BBwidget { /* ... */ }; // for BB
class Ival_slider
: public Ival_box, protected CWwidget { /* ... */ }; // for CW
// ...

There is no way of having the Ival_slider for BBwidgets coexist with the Ival_slider for CWwidgets, even if the two user-interface systems could themselves coexist. The obvious solution is to define several different Ival_slider classes with separate names:

class Ival_box { /* ... */ };
class BB_ival_slider
: public Ival_box, protected BBwidget { /*
... */ };
class CW_ival_slider
: public Ival_box, protected CWwidget { /*
... */ };
//
...

or graphically:

Image

To further insulate our application-oriented Ival_box classes from implementation details, we can derive an abstract Ival_slider class from Ival_box and then derive the system-specific Ival_sliders from that:

class Ival_box { /* ... */ };
class Ival_slider
: public Ival_box { /*
... */ };
class BB_ival_slider
: public Ival_slider, protected BBwidget { /*
... */ };
class CW_ival_slider
: public Ival_slider, protected CWwidget { /*
... */ };
//
...

or graphically:

Image

Usually, we can do better yet by utilizing more specific classes in the implementation hierarchy. For example, if the “Big Bucks Inc.” system has a slider class, we can derive our Ival_slider directly from the BBslider:

class BB_ival_slider
: public Ival_slider, protected BBslider { /* ... */ };
class CW_ival_slider

: public Ival_slider, protected CWslider { /* ... */ };

or graphically:

Image

This improvement becomes significant where – as is not uncommon – our abstractions are not too different from the ones provided by the system used for implementation. In that case, programming is reduced to mapping between similar concepts. Derivation from general base classes, such as BBwidget, is then done only rarely.

The complete hierarchy will consist of our original application-oriented conceptual hierarchy of interfaces expressed as derived classes:

class Ival_box { /* ... */ };
class Ival_slider
: public Ival_box { /*
... */ };
class Ival_dial
: public Ival_box { /*
... */ };
class Flashing_ival_slider
: public Ival_slider { /*
... */ };
class Popup_ival_slider
: public Ival_slider { /*
... */ };

followed by the implementations of this hierarchy for various graphical user interface systems, expressed as derived classes:

class BB_ival_slider
: public Ival_slider, protected BBslider { /*
... */ };
class BB_flashing_ival_slider
: public Flashing_ival_slider, protected BBwidget_with_bells_and_whistles { /*
... */ };
class BB_popup_ival_slider
: public Popup_ival_slider, protected BBslider { /*
... */ }; class CW_ival_slider
: public Ival_slider, protected CWslider { /*
... */ };
//
...

Using obvious abbreviations, this hierarchy can be represented graphically like this:

Image

The original Ival_box class hierarchy appears unchanged surrounded by implementation classes.

21.2.3.1. Critique

The abstract class design is flexible and almost as simple to deal with as the equivalent design that relies on a common base defining the user-interface system. In the latter design, the windows class is the root of a tree. In the former, the original application class hierarchy appears unchanged as the root of classes that supply its implementations. From the application’s point of view, these designs are equivalent in the strong sense that almost all code works unchanged and in the same way in the two cases. In either case, you can look at the Ival_box family of classes without bothering with the window-related implementation details most of the time. For example, we would not need to rewrite interact() from §21.2.1 if we switched from one class hierarchy to the other.

In either case, the implementation of each Ival_box class must be rewritten when the public interface of the user-interface system changes. However, in the abstract class design, almost all user code is protected against changes to the implementation hierarchy and requires no recompilation after such a change. This is especially important when the supplier of the implementation hierarchy issues a new “almost compatible” release. In addition, users of the abstract class hierarchy are in less danger of being locked into a proprietary implementation than are users of a classical hierarchy. Users of the Ival_box abstract class application hierarchy cannot accidentally use facilities from the implementation because only facilities explicitly specified in the Ival_box hierarchy are accessible; nothing is implicitly inherited from an implementation-specific base class.

The logical conclusion of this line of thought is a system represented to users as a hierarchy of abstract classes and implemented by a classical hierarchy. In other words:

• Use abstract classes to support interface inheritance (§3.2.3, §20.1).

• Use base classes with implementations of virtual functions to support implementation inheritance (§3.2.3, §20.1).

21.2.4. Localizing Object Creation

Most of an application can be written using the Ival_box interface. Further, should the derived interfaces evolve to provide more facilities than plain Ival_box, then most of an application can be written using the Ival_box, Ival_slider, etc., interfaces. However, the creation of objects must be done using implementation-specific names such as CW_ival_dial and BB_flashing_ival_slider. We would like to minimize the number of places where such specific names occur, and object creation is hard to localize unless it is done systematically.

As usual, the solution is to introduce an indirection. This can be done in many ways. A simple one is to introduce an abstract class to represent the set of creation operations:

class Ival_maker {
public:
virtual Ival_dial* dial(int, int) =0; //
make dial
virtual Popup_ival_slider* popup_slider(int, int) =0; // make popup slider
// ...
};

For each interface from the Ival_box family of classes that a user should know about, class Ival_maker provides a function that makes an object. Such a class is sometimes called a factory, and its functions are (somewhat misleadingly) sometimes called virtual constructors20.3.6).

We now represent each user-interface system by a class derived from Ival_maker:

class BB_maker : public Ival_maker { // make BB versions
public:
Ival_dial* dial(int, int) override;
Popup_ival_slider* popup_slider(int, int) override;
//
...
};

class LS_maker : public Ival_maker { // make LS versions
public:
Ival_dial* dial(int, int) override;
Popup_ival_slider* popup_slider(int, int) override;
//
...
};

Each function creates an object of the desired interface and implementation type. For example:

Ival_dial* BB_maker::dial(int a, int b)
{
return new BB_ival_dial(a,b);
}

Ival_dial* LS_maker::dial(int a, int b)
{
return new LS_ival_dial(a,b);
}

Given an Ival_maker, a user can now create objects without having to know exactly which user-interface system is used. For example:

void user(Ival_maker& im)
{
unique_ptr<Ival_box> pb {im.dial(0,99)}; //
create appropriate dial
// ...
}

BB_maker BB_impl; // for BB users
LS_maker LS_impl; // for LS users

void driver()
{
user(BB_impl); //
use BB
user(LS_impl); // use LS
}

Passing arguments to such “virtual constructors” is a bit tricky. In particular, we cannot override the base class functions that represent the interface with different arguments in different derived classes. This implies that a fair bit of foresight is required to design the factory class’s interface.

21.3. Multiple Inheritance

As described in §20.1, inheritance aims to provide one of two benefits:

Shared interfaces: leading to less replication of code using classes and making such code more uniform. This is often called run-time polymorphism or interface inheritance.

Shared implementation: leading to less code and more uniform implementation code. This is often called implementation inheritance.

A class can combine aspects of these two styles.

Here, we explore more general uses of multiple base classes and examine more technical issues related to combining and accessing features from multiple base classes.

21.3.1. Multiple Interfaces

An abstract class (e.g., Ival_box; §21.2.2) is the obvious way to represent an interface. For an abstract class without mutable state, there really is little difference between single and multiple uses of a base class in a class hierarchy. The resolution of potential ambiguities is discussed in §21.3.3, §21.3.4, and §21.3.5. In fact, any class without mutable state can be used as an interface in a multiple-inheritance lattice without significant complications and overhead. The key observation is that a class without mutable state can be replicated if necessary or shared if that is desired.

The use of multiple abstract classes as interfaces is almost universal in object-oriented designs (in any language with a notion of an interface).

21.3.2. Multiple Implementation Classes

Consider a simulation of bodies orbiting the Earth in which orbiting objects are represented as object of class Satellite. A Satellite object would contain orbital, size, shape, albedo, density parameters, etc., and provide operations for orbital calculations, modifying attributes, etc. Examples ofsatellites would be rocks, debris from old space vehicles, communication satellites, and the International Space Station. These kinds of satellites would be objects of classes derived from Satellite. Such derived classes would add data members and functions and would override some ofSatellite’s virtual functions to adjust their meaning suitably.

Now assume that I want to display the results of these simulations graphically and that I had available a graphics system that used the (not uncommon) strategy of deriving objects to be displayed from a common base class holding graphical information. This graphics class would provide operations for placement on the screen, scaling, etc. For generality, simplicity, and to hide the details of the actual graphics system, I will refer to the class providing graphical (or in fact alternatively nongraphical) output Display.

We can now define a class of simulated communication satellites, class Comm_sat:

class Comm_sat : public Satellite, public Displayed {
public:
//
...
};

or graphically:

Image

In addition to whatever operations are defined specifically for a Comm_sat, the union of operations on Satellite and Displayed can be applied. For example:

void f(Comm_sat& s)
{
s.draw(); //
Displayed::draw()
Pos p = s.center(); // Satellite::center()
s.transmit(); // Comm_sat::transmit()
}

Similarly, a Comm_sat can be passed to a function that expects a Satellite and to a function that expects Displayed. For example:

void highlight(Displayed*);
Pos center_of_gravity(const Satellite*);

void g(Comm_sat* p)
{
highlight(p); //
pass a pointer to the Displayed part of the Comm_sat
Pos x = center_of_gravity(p); // pass a pointer to the Satellite part of the Comm_sat
}

The implementation of this clearly involves some (simple) compiler technique to ensure that functions expecting a Satellite see a different part of a Comm_sat than do functions expecting a Displayed. Virtual functions work as usual. For example:

class Satellite {
public:
virtual Pos center() const = 0; //
center of gravity
// ...
};

class Displayed {
public:
virtual void draw() = 0;
//
...
};

class Comm_sat : public Satellite, public Displayed {
public:
Pos center() const override; //
override Satellite::center()
void draw() override; // override Displayed::draw()
// ...
};

This ensures that Comm_sat::center() and Displayed::draw() will be called for a Comm_sat treated as a Comm_sat and a Displayed, respectively.

Why didn’t I just keep the Satellite and Displayed parts of a Comm_sat completely separate? I could have defined Comm_sat to have a Satellite member and a Displayed member. Alternatively, I could have defined Comm_sat to have a Satellite* member and a Displayed* member and let its constructor set up the proper connections. For many design problems, I would do just that. However, the system that inspired this example was built on the idea of a Satellite class with virtual functions and a (separately designed) Displayed class with virtual functions. You provided your own satellites and your own displayed objects through derivation. In particular, you had to override Satellite virtual member functions and Displayed virtual member functions to specify the behavior of your own objects. That is the situation in which multiple inheritance of base classes with state and implementation is hard to avoid. Workarounds can be painful and hard to maintain.

The use of multiple inheritance to “glue” two otherwise unrelated classes together as part of the implementation of a third class is crude, effective, and relatively important, but not very interesting. Basically, it saves the programmer from writing a lot of forwarding functions (to compensate for the fact that we can only override functions defined in bases). This technique does not affect the overall design of a program significantly and can occasionally clash with the wish to keep implementation details hidden. However, a technique doesn’t have to be clever to be useful.

I generally prefer to have a single implementation hierarchy and (where needed) several abstract classes providing interfaces. This is typically more flexible and leads to systems that are easier to evolve. However, you can’t always get that – especially if you need to use existing classes that you don’t want to modify (e.g., because they are parts of someone else’s library).

Note that with single inheritance (only), the programmer’s choices for implementing the classes Displayed, Satellite, and Comm_sat would be limited. A Comm_sat could be a Satellite or a Displayed, but not both (unless Satellite was derived from Displayed or vice versa). Either alternative involves a loss of flexibility.

Why would anyone want a class Comm_sat? Contrary to some people’s conjectures, the Satellite example is real. There really was – and maybe there still is – a program constructed along the lines used to describe multiple implementation inheritance here. It was used to study the design of communication systems involving satellites, ground stations, etc. In fact, Satellite was derived from an early notion of a concurrent task. Given such a simulation, we can answer questions about communication traffic flow, determine proper responses to a ground station that is being blocked by a rainstorm, consider tradeoffs between satellite connections and Earth-bound connections, etc.

21.3.3. Ambiguity Resolution

Two base classes may have member functions with the same name. For example:

class Satellite {
public:
virtual Debug_info get_debug();
//
...
};

class Displayed {
public:
virtual Debug_info get_debug();
//
...
};

When a Comm_sat is used, these functions must be disambiguated. This can be done simply by qualifying a member name by its class name:

void f(Comm_sat& cs)
{
Debug_info di = cs.get_debug(); //
error: ambiguous
di = cs.Satellite::get_debug(); // OK
di = cs.Displayed::get_debug(); // OK
}

However, explicit disambiguation is messy, so it is usually best to resolve such problems by defining a new function in the derived class:

class Comm_sat : public Satellite, public Displayed {
public:
Debug_info get_debug() //
override Comm_sat::get_debug() and Displayed::get_debug()
{
Debug_info di1 = Satellite::get_debug();
Debug_info di2 = Displayed::get_debug();
return merge_info(di1,di2);
}
//
...
};

A function declared in a derived class overrides all functions of the same name and type in its base classes. Typically, that is exactly the right thing to do because it is generally a bad idea to use the same name for operations with different semantics in a single class. The ideal for virtual is for a call to have the same effect independently of which interface was used to find the function (§20.3.2).

In the implementation of an overriding function, it is often necessary to explicitly qualify the name to get the right version from a base class. A qualified name, such as Telstar::draw, can refer to a draw declared either in Telstar or in one of its base classes. For example:

class Telstar : public Comm_sat {
public:
void draw()
{
Comm_sat::draw(); //
finds Displayed::draw
// ... own stuff ...
}
//
...
};

or graphically:

Image

If Comm_sat::draw doesn’t resolve to a draw declared in Comm_sat, the compiler recursively looks in its base classes; that is, it looks for Satellite::draw and Displayed::draw, and if necessary looks in their base classes. If exactly one match is found, that name will be used. Otherwise,Comm_sat::draw is either not found or is ambiguous.

If, in Telstar::draw(), I had said plain draw(), the result would have been an “infinite” recursive call of Telstar::draw().

I could have said Displayed::draw(), but now the code would be subtly broken if someone added a Comm_sat::draw(); it is generally better to refer to a direct base class than to an indirect base class. I could have said Comm_sat::Displayed::draw(), but that would have been redundant. Had I said Satellite::draw(), the result would have been an error because the draw is over on the Displayed branch of the class hierarchy.

The get_debug() example basically assumes that at least some parts of Satellite and Displayed have been designed together. Getting an exact match of names, return types, argument types, and semantics by accident is extremely unlikely. It is far more likely that similar functionality is provided in different ways so that it takes effort to merge it into something that can be used together. We might originally have been presented with two classes SimObj and Widget that we could not modify, didn’t exactly provide what we needed, and where they did provide what we needed, did so through incompatible interfaces. In that case, we might have designed Satellite and Displayed as our interface classes, providing a “mapping layer” for our higher-level classes to use:

class Satellite : public SimObj {
//
map SimObj facilities to something easier to use for Satellite simulation
public:
virtual Debug_info get_debug(); //
call SimObj::DBinf() and extract information
// ...
};

class Displayed : public Widget {
//
map Widget facilities to something easier to use to display Satellite simulation results
public:
virtual Debug_info get_debug(); //
read Widget data and compose Debug_info
// ...
};

or graphically:

Image

Interestingly enough, this is exactly the technique we would use to disambiguate in the unlikely case where two base classes provided operations with exactly the same name, but with different semantics: add an interface layer. Consider the classical (but mostly hypothetical/theoretical) example of a class of a draw() member function in a video game involving cowboys:

class Window {
public:
void draw(); //
display image
// ...
};

class Cowboy {
public:
void draw(); //
pull gun from holster
// ...
};

class Cowboy_window : public Cowboy, public Window {
//
...
};

How do we override Cowboy::draw() and Window::draw()? These two functions have radically different meanings (semantics) but are identical in name and type; we need to override them by two separate functions. There is no direct language solution to this (exotic) problem, but adding intermediate classes will do:

struct WWindow : Window {
using Window::Window; //
inherit constructors
virtual void win_draw() = 0; // force derived class to override
void draw() override final { win_draw(); } // display image
};

struct CCowboy : Cowboy{
using Cowboy::Cowboy; //
inherit constructors
virtual void cow_draw() = 0; // force derived class to override
void draw() override final { cow_draw(); } // pull gun from holster
};

class Cowboy_window : public CCowboy, public WWindow {
public:
void cow_draw() override;
void win_draw() override;
//
...
};

Or graphically:

Image

Had the designer of Window been a bit more careful and specified draw() to be const, the whole problem would have evaporated. I find that fairly typical.

21.3.4. Repeated Use of a Base Class

When each class has only one direct base class, the class hierarchy will be a tree, and a class can only occur once in the tree. When a class can have multiple base classes, a class can appear multiple times in the resulting hierarchy. Consider a class providing facilities for storing state in a file (e.g., for breakpointing, debug information, or persistence) and restoring it later:

struct Storable { // persistent storage
virtual string get_file() = 0;
virtual void read() = 0;
virtual void write() = 0;

virtual ~Storable() { }
};

Such a useful class will naturally be used in several places in a class hierarchy. For example:

class Transmitter : public Storable {
public:
void write() override;
//
...
};

class Receiver : public Storable {
public:
void write() override;
//
...
};

class Radio : public Transmitter, public Receiver {
public:
string get_file() override;
void read() override;
void write() override;
//
...
};

Given that, we could imagine two cases:

[1] A Radio object has two subobjects of class Storable (one for Transmitter and one for Receiver).

[2] A Radio object has one subobject of class Storable (shared by Transmitter and Receiver).

The default, provided for the example as written, is two subobjects. Unless you state otherwise, you get one copy for each time you mention a class as a base. Graphically, we can represent that like this:

Image

A virtual function of a replicated base class can be overridden by a (single) function in a derived class. Typically, an overriding function calls its base class versions and then does the work specific to the derived class:

void Radio::write()
{
Transmitter::write();
Receiver::write();
//
... write radio-specific information ...
}

Casting from a replicated base class to a derived class is discussed in §22.2. For a technique for overriding each of the write() functions with separate functions from derived classes, see §21.3.3.

21.3.5. Virtual Base Classes

The Radio example in the previous subsection works because class Storable can be safely, conveniently, and efficiently replicated. The reason for that is simply that Storable is an abstract class providing a pure interface. A Storable object holds no data of its own. This is the simplest case and the one that offers the best separation of interface and implementation concerns. In fact, a class could not without some difficulty determine that there were two Storable subobjects on a Radio.

What if Storable did hold data and it was important that it should not be replicated? For example, we might define Storable to hold the name of the file to be used for storing the object:

class Storable {
public:
Storable(const string& s); //
store in file named s
virtual void read() = 0;
virtual void write() = 0;
virtual ~Storable();
protected:
string file_name;

Storable(const Storable&) = delete;
Storable& operator=(const Storable&) = delete;
};

Given this apparently minor change to Storable, we must change the design of Radio. All parts of an object must share a single copy of Storable. Otherwise, we could get two parts of something derived from Storable multiple times using different files. We avoid replication by declaring a base virtual: every virtual base of a derived class is represented by the same (shared) object. For example:

class Transmitter : public virtual Storable {
public:
void write() override;
//
...
};

class Receiver : public virtual Storable {
public:
void write() override;
//
...
};

class Radio : public Transmitter, public Receiver {
public:
void write() override;
//
...
};

Or graphically:

Image

Compare this diagram with the drawing of the Storable object in §21.3.4 to see the difference between ordinary inheritance and virtual inheritance. In an inheritance graph, every base class of a given name that is specified to be virtual will be represented by a single object of that class. On the other hand, each base class not specified virtual will have its own subobject representing it.

Why would someone want to use a virtual base containing data? I can think of three obvious ways for two classes in a class hierarchy to share data:

[1] Make the data nonlocal (outside the class as a global or namespace variable).

[2] Put the data in a base class.

[3] Allocate an object somewhere and give each of the two classes a pointer.

Option [1], nonlocal data, is usually a poor choice because we cannot control what code accesses the data and how. It breaks all notions of encapsulation and locality.

Option [2], put the data in a base class, is usually the simplest. However, for single inheritance that solution makes useful data (and functions) “bubble up” to a common base class; often it “bubbles” all the way to the root of an inheritance tree. This means that every member of the class hierarchy gets access. That is logically very similar to using nonlocal data and suffers from the same problems. So we need a common base that is not the root of a tree – that is, a virtual base.

Option [3], sharing an object accessed through pointers, makes sense. However, then constructor(s) need to set aside memory for that shared object, initialize it, and provide pointers to the shared object to objects needing access. That is roughly what constructors do to implement a virtual base.

If you don’t need sharing, you can do without virtual bases, and your code is often better and typically simpler for it. However, if you do need sharing within a general class hierarchy, you basically have a choice between using a virtual base and laboriously constructing your own variants of the idea.

We can represent an object of a class with a virtual base like this:

Image

The “pointers” to the shared object representing the virtual base, Storable, will be offsets, and often one of those can be optimized away by placing Storable in a fixed position relative to either the Receiver or the Transmitter subobject. Expect a storage overhead of one word for each virtual base.

21.3.5.1. Constructing Virtual Bases

Using virtual bases you can create complicated lattices. Naturally, we would prefer to keep the lattices simple, but however complicated we make them, the language ensures that a constructor of a virtual base is called exactly once. Furthermore, the constructor of a base (whether virtual or not) is called before its derived classes. Anything else would cause chaos (that is, an object might be used before it had been initialized). To avoid such chaos, the constructor of every virtual base is invoked (implicitly or explicitly) from the constructor for the complete object (the constructor for the most derived class). In particular, this ensures that a virtual base is constructed exactly once even if it is mentioned in many places in the class hierarchy. For example:

struct V {
V(int i);
//
...
};

struct A {
A(); //
default constructor
// ...
};

struct B : virtual V, virtual A {
B() :V{1} { /*
... */ }; // default constructor; must initialize base V
// ...
};

class C : virtual V {
public:
C(int i) : V{i} { /*
... */ }; // must initialize base V
// ...
};

class D : virtual public B, virtual public C {
//
implicitly gets the virtual base V from B and C
// implicitly gets virtual base A from B
public:
D() { /* ... */ } // error: no default constructor for C or V
D(int i) :C{i} { /* ... */ }; // error: no default constructor for V
D(int i, int j) :V{i}, C{j} { /* ... */ } // OK
// ...
};

Note that D can and must provide an initializer for V. The fact that V wasn’t explicitly mentioned as a base of D is irrelevant. Knowledge of a virtual base and the obligation to initialize it “bubbles up” to the most derived class. A virtual base is always considered a direct base of its most derived class. The fact that both B and C initialized V is irrelevant because the compiler has no idea which of those two initializers to prefer. Thus, only the initializer provided by the most derived class is used.

The constructor for a virtual base is called before the constructors for its derived classes.

In practice, this is not quite as localized as we would prefer. In particular, if we derive another class, DD, from D, then DD has to do work to initialize the virtual bases. Unless we can simply inherit D’s constructors (§20.3.5.1), that can be a nuisance. That ought to encourage us not to overuse virtual base classes.

This logical problem with constructors does not exist for destructors. They are simply invoked in reverse order of construction (§20.2.2). In particular, a destructor for a virtual base is invoked exactly once.

21.3.5.2. Calling a Virtual Class Member Once Only

When defining the functions for a class with a virtual base, the programmer in general cannot know whether the base will be shared with other derived classes. This can be a problem when implementing a service that requires a base class function to be called exactly once for each call of a derived function. Where needed, the programmer can simulate the scheme used for constructors by calling a virtual base class function only from the most derived class. For example, assume we have a basic Window class that knows how to draw its contents:

class Window {
public:
//
basic stuff
virtual void draw();
};

In addition, we have various ways of decorating a window and adding facilities:

class Window_with_border : public virtual Window {
//
border stuff
protected:
void own_draw(); //
display the border
public:
void draw() override;
};

class Window_with_menu : public virtual Window {
//
menu stuff
protected:
void own_draw(); //
display the menu
public:
void draw() override;
};

The own_draw() functions need not be virtual because they are meant to be called from within a virtual draw() function that “knows” the type of the object for which it was called.

From this, we can compose a plausible Clock class:

class Clock : public Window_with_border, public Window_with_menu {
//
clock stuff
protected:
void own_draw(); //
display the clock face and hands
public:
void draw() override;
};

or graphically:

Image

The draw() functions can now be defined using the own_draw() functions, so that a caller of any draw() gets Window::draw() invoked exactly once. This is done independently of the kind of Window for which draw() is invoked:

void Window_with_border::draw()
{
Window::draw();
own_draw(); //
display the border
}

void Window_with_menu::draw()
{
Window::draw();
own_draw(); //
display the menu
}

void Clock::draw()
{
Window::draw();
Window_with_border::own_draw();
Window_with_menu::own_draw();
own_draw(); //
display the clock face and hands
}

Note that a qualified call, such as Window::draw(), does not use the virtual call mechanism. Instead, it directly calls the explicitly named function, thus avoiding nasty infinite recursion.

Casting from a virtual base class to a derived class is discussed in §22.2.

21.3.6. Replicated vs. Virtual Bases

Using multiple inheritance to provide implementations for abstract classes representing pure interfaces affects the way a program is designed. Class BB_ival_slider21.2.3) is an example:

class BB_ival_slider
: public Ival_slider, // interface
protected BBslider // implementation
{
// implementation of functions required by Ival_slider and BBslider, using facilities from BBslider
};

In this example, the two base classes play logically distinct roles. One base is a public abstract class providing the interface, and the other is a protected concrete class providing implementation “details.” These roles are reflected in both the style of the classes and in the access control (§20.5) provided. The use of multiple inheritance is close to essential here because the derived class needs to override virtual functions from both the interface and the implementation.

For example, consider again the Ival_box classes from §21.2.1. In the end (§21.2.2), I made all the Ival_box classes abstract to reflect their role as pure interfaces. Doing that allowed me to place all implementation details in specific implementation classes. Also, all sharing of implementation details was done in the classical hierarchy of the windows system used for the implementation.

When using an abstract class (without any shared data) as an interface, we have a choice:

• Replicate the interface class (one object per mention in the class hierarchy).

• Make the interface class virtual to share a simple object among all classes in the hierarchy that mention it.

Using Ival_slider as a virtual base gives us:

class BB_ival_slider
: public virtual Ival_slider, protected BBslider { /*
... */ };
class Popup_ival_slider
: public virtual Ival_slider { /*
... */ };
class BB_popup_ival_slider
: public virtual Popup_ival_slider, protected BB_ival_slider { /*
... */ };

or graphically:

Image

It is easy to imagine further interfaces derived from Popup_ival_slider and further implementation classes derived from such classes and BB_popup_ival_slider.

However, we also have this alternative using replicated Ival_slider objects:

class BB_ival_slider
: public Ival_slider, protected BBslider { /*
... */ };
class Popup_ival_slider
: public Ival_slider { /*
... */ };
class BB_popup_ival_slider
: public Popup_ival_slider, protected BB_ival_slider { /*
... */ };

or graphically:

Image

Surprisingly, there are no fundamental run-time or space advantages to one design over the other. There are logical differences, though. In the replicated Ival_slider design, a BB_popup_ival_slider can’t be implicitly converted to an Ival_slider (because that would be ambiguous):

void f(Ival_slider* p);

void g(BB_popup_ival_slider* p)
{
f(p); //
error: Popup_ival_slider::Ival_slider or BB_ival_slider::Ival_slider?
}

On the other hand, it is possible to construct plausible scenarios where the sharing implied in the virtual base design causes ambiguities for casts from the base class (§22.2). However, such ambiguities are easily dealt with.

How do we choose between virtual base classes and replicated base classes for our interfaces? Most often, of course, we don’t get a choice because we have to conform to an existing design. When we do have a choice, we can take into account that (surprisingly) the replicated base solution tends to lead to slightly smaller objects (because there is no need for data structures supporting sharing) and that we often get our interface objects from “virtual constructors” or “factory functions” (§21.2.4). For example:

Popup_ival_slider* popup_slider_factory(args)
{
//
...
return new BB_popup_ival_slider(args);
//
...
}

No explicit conversion is needed to get from an implementation (here, BB_popup_ival_slider) to its direct interfaces (here, Popup_ival_slider).

21.3.6.1. Overriding Virtual Base Functions

A derived class can override a virtual function of its direct or indirect virtual base class. In particular, two different classes might override different virtual functions from the virtual base. In that way, several derived classes can contribute implementations to the interface presented by a virtual base class. For example, the Window class might have functions set_color() and prompt(). In that case, Window_with_border might override set_color() as part of controlling the color scheme, and Window_with_menu might override prompt() as part of its control of user interactions:

class Window {
//
...
virtual void set_color(Color) = 0; // set background color
virtual void prompt() = 0;
};

class Window_with_border : public virtual Window {
//
...
void set_color(Color) override; // control background color
};

class Window_with_menu : public virtual Window {
//
...
void prompt() override; // control user interactions
};

class My_window : public Window_with_menu, public Window_with_border {
// ...
};

What if different derived classes override the same function? This is allowed if and only if some overriding class is derived from every other class that overrides the function. That is, one function must override all others. For example, My_window could override prompt() to improve on what Window_with_menu provides:

class My_window : public Window_with_menu, public Window_with_border {
//
...
void prompt() override; // don't leave user interactions to base
};

or graphically:

Image

If two classes override a base class function, but neither overrides the other, the class hierarchy is an error. The reason is that no single function can be used to give a consistent meaning for all calls independently of which class they use as an interface. Or, using implementation terminology, no virtual function table can be constructed because a call to that function on the complete object would be ambiguous. For example, had Radio in §21.3.5 not declared write(), the declarations of write() in Receiver and Transmitter would have caused an error when defining Radio. As with Radio, such a conflict is resolved by adding an overriding function to the most derived class.

A class that provides some – but not all – of the implementation for a virtual base class is often called a mixin.

21.4. Advice

[1] Use unique_ptr or shared_ptr to avoid forgetting to delete objects created using new; §21.2.1.

[2] Avoid date members in base classes intended as interfaces; §21.2.1.1.

[3] Use abstract classes to express interfaces; §21.2.2.

[4] Give an abstract class a virtual destructor to ensure proper cleanup; §21.2.2.

[5] Use override to make overriding explicit in large class hierarchies; §21.2.2.

[6] Use abstract classes to support interface inheritance; §21.2.2.

[7] Use base classes with data members to support implementation inheritance; §21.2.2.

[8] Use ordinary multiple inheritance to express a union of features; §21.3.

[9] Use multiple inheritance to separate implementation from interface; §21.3.

[10] Use a virtual base to represent something common to some, but not all, classes in a hierarchy; §21.3.5.