Although the derivation list of a class may not include the same base class more than once, a class can inherit from the same base class more than once. It might inherit the same base indirectly from two of its own direct base classes, or it might inherit a particular class directly and indirectly through another of its base classes.
As an example, the IO library istream
and ostream
classes each inherit from a common abstract base class named basic_ios
. That class holds the stream’s buffer and manages the stream’s condition state. The class iostream
, which can both read and write to a stream, inherits directly from both istream
and ostream
. Because both types inherit from basic_ios
, iostream
inherits that base class twice, once through istream
and once through ostream
.
By default, a derived object contains a separate subpart corresponding to each class in its derivation chain. If the same base class appears more than once in the derivation, then the derived object will have more than one subobject of that type.
This default doesn’t work for a class such as iostream
. An iostream
object wants to use the same buffer for both reading and writing, and it wants its condition state to reflect both input and output operations. If an iostream
object has two copies of its basic_ios
class, this sharing isn’t possible.
In C++ we solve this kind of problem by using virtual inheritance. Virtual inheritance lets a class specify that it is willing to share its base class. The shared base-class subobject is called a virtual base class. Regardless of how often the same virtual base appears in an inheritance hierarchy, the derived object contains only one, shared subobject for that virtual base class.
Panda
ClassIn the past, there was some debate as to whether panda belongs to the raccoon or the bear family. To reflect this debate, we can change Panda
to inherit from both Bear
and Raccoon
. To avoid giving Panda
two ZooAnimal
base parts, we’ll define Bear
and Raccoon
to inherit virtually from ZooAnimal
. Figure 18.3 illustrates our new hierarchy.
Looking at our new hierarchy, we’ll notice a nonintuitive aspect of virtual inheritance. The virtual derivation has to be made before the need for it appears. For example, in our classes, the need for virtual inheritance arises only when we define Panda
. However, if Bear
and Raccoon
had not specified virtual
on their derivation from ZooAnimal
, the designer of the Panda
class would be out of luck.
In practice, the requirement that an intermediate base class specify its inheritance as virtual rarely causes any problems. Ordinarily, a class hierarchy that uses virtual inheritance is designed at one time either by one individual or by a single project design group. It is exceedingly rare for a class to be developed independently that needs a virtual base in one of its base classes and in which the developer of the new base class cannot change the existing hierarchy.
Virtual derivation affects the classes that subsequently derive from a class with a virtual base; it doesn’t affect the derived class itself.
We specify that a base class is virtual by including the keyword virtual
in the derivation list:
// the order of the keywords public and virtual is not significant
class Raccoon : public virtual ZooAnimal { /* ... */ };
class Bear : virtual public ZooAnimal { /* ... */ };
Here we’ve made ZooAnimal
a virtual base class of both Bear
and Raccoon
.
The virtual
specifier states a willingness to share a single instance of the named base class within a subsequently derived class. There are no special constraints on a class used as a virtual base class.
We do nothing special to inherit from a class that has a virtual base:
class Panda : public Bear,
public Raccoon, public Endangered {
};
Here Panda
inherits ZooAnimal
through both its Raccoon
and Bear
base classes. However, because those classes inherited virtually from ZooAnimal, Panda
has only one ZooAnimal
base subpart.
An object of a derived class can be manipulated (as usual) through a pointer or a reference to an accessible base-class type regardless of whether the base class is virtual. For example, all of the following Panda
base-class conversions are legal:
void dance(const Bear&);
void rummage(const Raccoon&);
ostream& operator<<(ostream&, const ZooAnimal&);
Panda ying_yang;
dance(ying_yang); // ok: passes Panda object as a Bear
rummage(ying_yang); // ok: passes Panda object as a Raccoon
cout << ying_yang; // ok: passes Panda object as a ZooAnimal
Because there is only one shared subobject corresponding to each shared virtual base, members in that base can be accessed directly and unambiguously. Moreover, if a member from the virtual base is overridden along only one derivation path, then that overridden member can still be accessed directly. If the member is overridden by more than one base, then the derived class generally must define its own version as well.
For example, assume class B
defines a member named x
; class D1
inherits virtually from B
as does class D2;
and class D
inherits from D1
and D2
. From the scope of D
, x
is visible through both of its base classes. If we use x
through a D
object, there are three possibilities:
• If x
is not defined in either D1
or D2
it will be resolved as a member in B
; there is no ambiguity. A D
object contains only one instance of x
.
• If x
is a member of B
and also a member in one, but not both, of D1
and D2
, there is again no ambiguity—the version in the derived class is given precedence over the shared virtual base class, B
.
• If x
is defined in both D1
and D2
, then direct access to that member is ambiguous.
As in a nonvirtual multiple inheritance hierarchy, ambiguities of this sort are best resolved by the derived class providing its own instance of that member.
Exercise 18.28: Given the following class hierarchy, which inherited members can be accessed without qualification from within the VMI
class? Which require qualification? Explain your reasoning.
struct Base {
void bar(int); // public by default
protected:
int ival;
};
struct Derived1 : virtual public Base {
void bar(char); // public by default
void foo(char);
protected:
char cval;
};
struct Derived2 : virtual public Base {
void foo(int); // public by default
protected:
int ival;
char cval;
};
class VMI : public Derived1, public Derived2 { };