Your design challenge
On one side, you have an abstraction IShape
, and more specialized abstractions like IShapeConvex
, IShapeHullConvex
. On the other side, you have implementations of these abstractions BtShape
, BtShapeConvex
, and BtShapeHullConvex
.
No multiple inheritance ?
If the world was simple:
Either the abstraction would only be an interface. In this case each implementation (BtShape
, XyzShape
, ...) would have to recreate the common behavior that you currently implement in IShape
. If the common behavior is significant, welcome DRY !
Or each implementation would only inherit one abstraction. This would mean that for example BtShape
would inherit from IShape
. In this case BtShapeConvex
would inherit from IShapeConvex
and therewith from IShape
, but not from BtShape
. This means that it would have to provide all the functionality of a BtShape
on its own. Welcome DRY !
If the world is as complex as it is, and without multiple inheritance, you would probably address this kind of challenge with the bridge design pattern:
- This pattern aims at separating the abstraction and its specializations on one side, and the implementation and its specialization on the other side. The bridge between the two is organized using composition.
- But despite it's conceptual beauty, it requires to think well the API and it's a bit cumbersome to use with a lot of boilerplate code to forward the calls from abstraction to implementation.
With multiple inheritance
We could argue about whether we really need MI or not. But let's face the reality: in your case that a BtConvexShape
IS really BtShape
; and at the same time it IS really an IConvexShape
and also an IShape
.
MI allows you to express this complex semantical relationship in a simple manner. It also allows to define each behavior where best suits according to the semantic and not where imposed by the language constraints.
The drawback is that MI is difficult for many people to understand. And it comes with conceptual problems related to the diamond: a BtConvexShape
object is an IConvexShape
which is an IShape
object, and at the same time it's a BtShape
which is an IShape
object. The question is whether we have two independent IShape
objects, or if all these IShape
always refer to the same.
C++ has virtual inheritance to make the difference between the second case well. Your example is a typical use case for MI with virtual inheritance. It's exatly what this language feature was designed for !
The main issues with the virtual inheritance is that:
- maintainers have to be familiar with it.
- it's error prone: you could easily forget a virtual inheritance in your hierarchy, which could result in two distinct base objects in some exotic special case, when you assume there is only one. This is a very nasty bug.
- it's constraining, since you have to take care of the virtual base object in the constructors accross the hierarchy.
But if your design correspond to the typical case of multiple and virtual inheritance, and if you managed to get it work, go for it. It's like all powerful tools: you just have to be extra-careful and it'll serve you well.