I often use pure virtual classes (interfaces) to reduce dependencies between implementations of different classes in my current project. It is not unusual for me to even have hierarchies in which I have pure virtual and non-pure virtual classes that extend other pure virtual classes. Here is an example of such a situation:
class Engine
{ /* Declares pure virtual methods only */ }
class RunnableEngine : public virtual Engine
{ /* Defines some of the methods declared in Engine */ }
class RenderingEngine : public virtual Engine
{ /* Declares additional pure virtual methods only */ }
class SimpleOpenGLRenderingEngine : public RunnableEngine,
public virtual RenderingEngine
{ /* Defines the methods declared in Engine and RenderingEngine (that are not
already taken care of by RunnableEngine) */ }
Both RunnableEngine
and RenderingEngine
extend Engine
virtually so that the diamond problem does not affect SimpleOpenGLRenderingEngine
.
I want to take a preventative stance against the diamond problem instead of dealing with it when it becomes a problem, especially since I like to write code that is as easy for someone else to use as possible and I don't want them to have to modify my classes so that they can create particular class heirarchies e.g. if Bob wanted to do this:
class BobsRenderingEngine : public virtual RenderingEngine
{ /* Declares additional pure virtual methods only */ }
class BobsOpenGLRenderingEngine : public SimpleOpenGLRenderingEngine,
public BobsRenderingEngine
{ /* Defines the methods declared in BobsRenderingEngine */ }
This would not be possible if I had not made SimpleOpenGLRenderingEngine
extend RenderingEngine
virtually. I am aware that the probability of Bob wanting to do this may be very low.
So, I have begun using the convention of always extending pure virtual classes virtually so that multiple inheritance from them does not cause the diamond problem. Maybe this is due to me coming from Java and tending to only use single inheritance with non-pure virtual classes. I'm sure it is probably overkill in some situations but are there any downsides to using this convention? Could this cause any problems with performance/functionality etc.? If not I don't see a reason not to use the convention, even if it may often not be needed in the end.
I would say that, in general, encouraging inheritance is a bad idea (and that's what you are doing by using virtual inheritance). Few things are actually well represented with a tree structure which is implied by inheritance. In addition I find that multiple inheritance tend to break the single responsibility rule. I do not know your exact use case and I agree that at times we have no other choice.
However in C++ we have another way to compose objects, encapsulation. I find that the declaration below is far easier to understand and manipulate:
class SimpleOpenGLRenderingEngine
{
public:
RunnableEngine& RunEngine() { return _runner; }
RenderingEngine& Renderer() { return _renderer; }
operator RunnableEngine&() { return _runner; }
operator RenderingEngine&() { return _renderer; }
private:
RunnableEngine _runner;
RenderingEngine _renderer;
};
It is true that the object will use more memory than with virtual inheritance, but I doubt objects that complex are created in massive numbers.
Now let's say you really want to inherit, for some external constraint. Virtual inheritance is still hard to manipulate: you are probably comfortable with it, but people deriving from your classes may not, and will probably not think too much before deriving. I guess a better choice would be to use private inheritance.
class RunnableEngine : private Engine
{
Engine& GetEngine() { return *this; }
operator Engine&() { return *this; }
};
// Similar implementation for RenderingEngine
class SimpleOpenGLRenderingEngine : public RunnableEngine, public RenderingEngine
{ };
Short answer: Yes. You nailed it.
Long answer:
So, I have begun using the convention of always extending pure virtual classes
Note: the proper term is abstract classes, not "pure virtual classes".
I have begun using the convention of always extending [abstract] classes virtually so that multiple inheritance from them does not cause the diamond problem.
Indeed. This is a sound convention, but not just for abstract classes, for all classes which represents an interface, some of which have virtual functions with default implementations.
(The shortcomings of Java force you to only put pure virtual functions and no data member in "interfaces", C++ is more flexible, for good, as usual.)
Could this cause any problems with performance
Virtual-ness has a cost.
Profile your code.
Could this cause any problems with [] functionality etc.?
Possibly (rarely).
You cannot unvirtualise a base class: if some specific derived class needs to have two distinct base class subobjects, you cannot use inheritance for both branches, you need containment.
Well, it's pretty simple. You violated the the single responsibility principle (SPR) and this is the cause of your problem. Your "Engine" class is a God class. A well-designed hierarchy does not invoke this problem.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With