2

I want to use a physics engine (like bullet or PhysX) in my program, however I want to hide the actual physics engine from it, so I can easily swap it out with another during run-time (e.g. switch between bullet and PhysX without requiring any changes to my main program).

To do so, I have created some abstract classes, which will be used by the program directly:

class IShape
{
public:
    virtual ~IShape()=default;
};

class IConvexShape
    : virtual public IShape
{
public:
};

class IConvexHullShape
    : virtual public IConvexShape
{
public:
};

Then I can just implement these classes for a specific physics engine like bullet:

class BtShape
    : virtual public IShape
{
public:
};

class BtConvexShape
    : virtual public IConvexShape,
    public BtShape
{
public:
};

class BtConvexHullShape
    : virtual public IConvexHullShape,
    public BtConvexShape
{
public:
};

The problem is that IShape implements some base functionality that should be used for all other shape types, and BtShape implements some base functionality that should be used for all other bullet shape types, which means I have to use virtual inheritance (Same goes for IConvexShape / BtConvexShape.):

UML depiction of the class interface.

While this works, I'm a bit hesitant to go further with this, since it's usually recommended to avoid virtual inheritance altogether.

Can I expect any problems with this approach? Are there any alternative methods I can use instead?

1
  • 2
    That's exactly what virtual inheritance is for. Commented Jun 25, 2019 at 6:07

1 Answer 1

1

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.