C++ FAQ Celebrating Twenty-One Years of the C++ FAQ!!!
(Click here for a personal note from Marshall Cline.)
Section 25:
[25.5] Can you provide an example that demonstrates the above guidelines?

Suppose you have land vehicles, water vehicles, air vehicles, and space vehicles. (Forget the whole concept of amphibious vehicles for this example; pretend they don't exist for this illustration.) Suppose we also have different power sources: gas powered, wind powered, nuclear powered, pedal powered, etc. We could use multiple inheritance to tie everything together, but before we do, we should ask a few tough questions:

  1. Will the users of LandVehicle need to have a Vehicle& that refers to a LandVehicle object? In particular, will the users call methods on a Vehicle-reference and expect the actual implementation of those methods to be specific to LandVehicles?
  2. Ditto for GasPoweredVehicles: will the users want a Vehicle reference that refers to a GasPoweredVehicle object, and in particular will they want to call methods on that Vehicle reference and expect the implementations to get overridden by GasPoweredVehicle?

If both answers are "yes," multiple inheritance is probably the best way to go. But before you close the door on the alternatives, here are a few more "decision criteria." Suppose there are N geographies (land, water, air, space, etc.) and M power sources (gas, nuclear, wind, pedal, etc.). There are at least three choices for the overall design: the bridge pattern, nested generalization, and multiple inheritance. Each has its pros/cons:

  • With the bridge pattern, you create two distinct hierarchies: ABC Vehicle has derived classes LandVehicle, WaterVehicle, etc., and ABC Engine has derived classes GasPowered, NuclearPowered, etc. Then the Vehicle has an Engine* (that is, an Engine-pointer), and users mix and match vehicles and engines at run-time. This has the advantage that you only have to write N+M derived classes, which means things grow very gracefully: when you add a new geography (incrementing N) or engine type (incrementing M), you need add only one new derived class. However you have several disadvantages as well: you only have N+M derived classes which means you only have at most N+M overrides and therefore N+M concrete algorithms / data structures. If you ultimately want different algorithms and/or data structures in the N*M combinations, you'll have to work hard to make that happen, and you're probably better off with something other than a pure bridge pattern. The other thing the bridge doesn't solve for you is eliminating the nonsensical choices, such as pedal powered space vehicles. You can solve that by adding extra checks when the users combine vehicles and engines at run-time, but it requires a bit of skullduggery, something the bridge pattern doesn't provide for free. The bridge also restricts users since, although there is a common base class above all geographies (meaning a user can pass any kind of vehicle as a Vehicle&), there is not a common base class above, for example, all gas powered vehicles, and therefore users cannot pass any gas powered vehicle as a GasPoweredVehicle&. Finally, the bridge has the advantage that it shares code between the group of, for example, water vehicles as well as the group of, for example, gas powered vehicles. In other words, the various gas powered vehicles share the code in derived class GasPoweredEngine.
  • With nested generalization, you pick one of the hierarchies as primary and the other as secondary, and you have a nested hierarchy. For example, if you choose geography as primary, Vehicle would have derived classes LandVehicle, WaterVehicle, etc., and those would each have further derived classes, one per power source type. E.g., LandVehicle would have derived classes GasPoweredLandVehicle, PedalPoweredLandVehicle, NuclearPoweredLandVehicle, etc.; WaterVehicle would have a similar set of derived classes, etc. This requires you to write roughly N*M different derived classes, which means things don't grow gracefully when you increment N or M, but it gives you the advantage over the bridge that you can have N*M different algorithms and data structures. It also gives you fine granular control, since the user cannot select nonsensical combinations, such as pedal powered space vehicles, since the user can select only those combinations that a programmer has decided are reasonable. Unfortunately nested generalization doesn't improve the problem with passing any gas powered vehicle as a common base class, since there is no common base class above the secondary hierarchy, e.g., there is no GasPoweredVehicle base class. And finally, it's not obvious how to share code between all vehicles that use the same power source, e.g., between all gas powered vehicles.
  • With multiple inheritance, you have two distinct hierarchies, just like the bridge, but you remove the Engine* from the bridge and instead create roughly N*M derived classes below both the hierarchy of geographies and the hierarchy of power sources. It's not as simple as this, since you'll need to change the concept of the Engine classes. In particular, you'll want to rename the classes in that hierarchy from, for example, GasPoweredEngine to GasPoweredVehicle; plus you'll need to make corresponding changes to the methods in the hierarchy. In any case, class GasPoweredLandVehicle will multiply inherit from GasPoweredVehicle and LandVehicle, and similarly with GasPoweredWaterVehicle, NuclearPoweredWaterVehicle, etc. Like nested generalization, you have to write roughly N*M classes, which doesn't grow gracefully, but it does give you fine granular control over both which algorithm and data structures to use in the various derived classes as well as which combinations are deemed "reasonable," meaning you simply don't create nonsensical choices like PedalPoweredSpaceVehicle. It solves a problem shared by both bridge and nested generalization, namely it allows a user to pass any gas powered vehicle using a common base class. Finally it provides a solution to the code-sharing problem, a solution that is at least as good as that of the bridge solution: it lets all gas powered vehicles share common code when that is desired. We say this is "at least as good as the solution from the bridge" since, unlike the bridge, the derived classes can share common code within gas powered vehicles, but can also, unlike with the bridge, override and replace that code in cases where the shared code is not ideal.

The most important point: there is no universally "best" answer. Perhaps you were hoping I would tell you to always use one or the other of the above choices. I'd be happy to do that except for one minor detail: it'd be a lie. If exactly one of the above was always best, then one size would fit all, and we know it does not.

So here's what you have to do: T H I N K. You'll have to make a decision. I'll give you some guidelines, but ultimately you will have to decide what is best (or perhaps "least bad") for your situation.