Multiple Inheritance
In
Designing an RWCollectable Class we built a
Bus class by inheriting from
RWCollectable. If we had an existing
Bus class at hand, we might have saved some work by using multiple inheritance to create a new class with the functionality of both
Bus and
RWCollectable as follows:
class CollectableBus : public RWCollectable, public Bus {
.
.
.
};
This is the approach taken by many of the Rogue Wave collectable classes; for example, class
RWCollectableString inherits from both class
RWCollectable and class
RWCString. The general idea is to create your object first, then tack on the
RWCollectable class to make the whole thing collectable. This way, you will be able to use your objects for other things or in other situations, where you might not want to inherit from class
RWCollectable.
There is another good reason for using this approach: to avoid ambiguous base classes. Here's an example:
class A { };
class B : public A { };
class C : public A { };
class D : public B, public C { };
void fun(A&);
main () {
D d;
fun(d); // Which A ?
}
There are two approaches to disambiguating the call to fun(). We can either change it to:
fun((B)d); // We mean B's occurrence of A
or make A a virtual base class.
The first approach is error-prone because the user must know the details of the inheritance tree in order to make the proper cast.
The second approach, making A a virtual base class, solves this problem, but introduces another: it becomes nearly impossible to make a cast back to the derived class! This is because there are now two or more paths back through the inheritance hierarchy or, if you prefer a more physical reason, the compiler implements virtual base classes as pointers to the base class, and you can't follow a pointer backwards.
We could exhaustively search all possible paths in the object's inheritance hierarchy, looking for a match. (This is the approach of the NIH Classes.) However, this search is slow, even if speeded up by “memorizing” the resulting addresses, since it must be done for every cast. Since it is also bulky and always complicated, we decided that it was unacceptable.
Hence, we went back to the first route. This can be made acceptable if we keep the inheritance trees simple by not making everything derive from the same base class. Hence, rather than using a large secular base class with lots of functionality, we have chosen to tease out the separate bits of functionality into separate, smaller base classes.
The idea is to first build your object, and then tack on the base class that will supply the functionality you need, such as collectability. You thus avoid multiple base classes of the same type and the resulting ambiguous calls.