Is it a good convention to virtually inherit from pure virtual (interface) classes?
Asked Answered
I

3

5

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.

Inarch answered 3/3, 2012 at 21:46 Comment(10)
Yup, complete overkill. It is only a real diamond problem when you have multiple implementations to choose from. Not a problem with an interface, it doesn't have any implementation. Virtual inheritance is a band-aid, you only use it when your back is against the wall.Pollen
@HansPassant, I don't agree with you. Dimond problem may often occur even with pure virtual classes. Suppose you have an interface, say IA, and its default implementation AImpl : public virtual IA, then using virtual inheritance you may define a class B : public virtual IA, public AImpl. Thus B will use AImpl's implementation of IA.Shandy
@kids_fok Yup, that's exactly where I have the problem. Well, I'm not sure you need B to extend IA explicitly in your example by the example in my question is similar.Inarch
@Hans What if someone using my code as a library has his back against the wall because one of my classes doesn't inherit virtually? My code, his problem.Inarch
How could it be a real problem? You declare an interface, you didn't implement anything. The problem you are talking about has nothing to do with interfaces, it possibly has something to do with actually writing code to implement an interface. At that point, writing a clean no-implementation interface doesn't matter anymore. They drive from your class, not your interface. And have a phone number/email address to contact you.Pollen
@GaryBuyn, sorry, I have made a mistake. I wanted to describe a problem as follows: `class B: public virtual IB, public AImpl', where IB is derived from IAShandy
@Hans Thanks for your help, sorry I am (obviously) new to this topic so I am having a bit of trouble understanding. I think I may not have worded the question very well to show my intent. I have edited it now and maybe it is clearer?Inarch
No, really, there isn't a problem here. The diamond problem affects inheritance via 2 paths from a concrete class, not from an interface. What you have is fine.Sachiko
@AlanStokes "The diamond problem affects inheritance via 2 paths from a concrete class, not from an interface." This is absolutely wrong. You don't know what you are talking about.Euroclydon
@HansPassant "It is only a real diamond problem when you have multiple implementations to choose from." Wrong!Euroclydon
T
3

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
{ };
Tko answered 4/3, 2012 at 1:4 Comment(0)
E
2

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.

Euroclydon answered 21/7, 2012 at 1:50 Comment(0)
F
2

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.

Fibroblast answered 21/7, 2012 at 1:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.