Your design challenge
On one side, you have an abstraction
IShape, and more specialized abstractions like
IShapeHullConvex. On the other side, you have implementations of these abstractions
No multiple inheritance ?
If the world was simple:
Either the abstraction would only be an interface. In this case each implementation (
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
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.