我经常使用纯虚拟类(接口)来减少当前项目中不同类的实现之间的依赖关系。我甚至拥有层次结构并不罕见,在这些层次结构中我有纯虚拟和非纯虚拟类,可以扩展其他纯虚拟类。以下是这种情况的一个例子:
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) */ }
RunnableEngine
和RenderingEngine
虚拟地延伸Engine
,以便钻石问题不会影响SimpleOpenGLRenderingEngine
。
我想对钻石问题采取预防性的立场,而不是在它成为问题时处理它,特别是因为我喜欢编写尽可能容易让别人使用的代码,我不想要它们必须修改我的类,以便他们可以创建特定的类heirarchies,例如如果鲍勃想要这样做:
class BobsRenderingEngine : public virtual RenderingEngine
{ /* Declares additional pure virtual methods only */ }
class BobsOpenGLRenderingEngine : public SimpleOpenGLRenderingEngine,
public BobsRenderingEngine
{ /* Defines the methods declared in BobsRenderingEngine */ }
如果我没有虚拟地SimpleOpenGLRenderingEngine
延长RenderingEngine
,那么这是不可能的。 我知道Bob想要这样做的可能性非常低。
所以,我已经开始使用永远扩展纯虚拟类的约定,以便从它们的多重继承不会导致钻石问题。也许这是因为我来自Java并且倾向于仅使用非纯虚拟类的单继承。我确信在某些情况下它可能有点过分但是使用这个约定有什么缺点吗?这会导致性能/功能等问题吗?如果不是,我没有看到不使用该惯例的理由,即使最终可能不需要它。
答案 0 :(得分:2)
我想说,一般来说,鼓励继承是一个坏主意(这就是你通过使用虚拟继承所做的事情)。实际上很少有东西能够通过继承隐含的树结构来表示。此外,我发现多重继承倾向于打破单一责任规则。我不知道您的确切用例,我同意有时我们别无选择。
然而,在C ++中,我们有另一种组合对象的方法,封装。我发现下面的声明更容易理解和操纵:
class SimpleOpenGLRenderingEngine
{
public:
RunnableEngine& RunEngine() { return _runner; }
RenderingEngine& Renderer() { return _renderer; }
operator RunnableEngine&() { return _runner; }
operator RenderingEngine&() { return _renderer; }
private:
RunnableEngine _runner;
RenderingEngine _renderer;
};
对象确实会使用比使用虚拟继承更多的内存,但我怀疑那些复杂的对象是大量创建的。
现在让我们说你真的想继承一些外部约束。虚拟继承仍然难以操纵:你可能对它感到满意,但是从你的类派生的人可能不会,并且在推导之前可能不会想太多。我想更好的选择是使用私有继承。
class RunnableEngine : private Engine
{
Engine& GetEngine() { return *this; }
operator Engine&() { return *this; }
};
// Similar implementation for RenderingEngine
class SimpleOpenGLRenderingEngine : public RunnableEngine, public RenderingEngine
{ };
答案 1 :(得分:2)
简短回答:是的。你钉了它。
答案很长:
所以,我已经开始使用总是扩展纯虚拟类的惯例
注意:正确的术语是抽象类,而不是"纯虚拟类"。
我已经开始使用永远扩展[abstract]类的约定,以便从它们的多重继承不会导致钻石问题。
事实上。这是一个合理的约定,但不仅适用于抽象类,适用于代表接口的所有类,其中一些类具有默认实现的虚函数。
(Java的缺点迫使你只把纯虚函数和没有数据成员放在"接口"中,C ++比往常更灵活,好。)
这会导致性能方面的任何问题
Virtual-ness有成本。
描述您的代码。
这会导致[]功能等问题吗?
可能(很少)。
您无法对基类进行非虚拟化:如果某个特定的派生类需要具有两个不同的基类子对象,则不能对两个分支使用继承,则需要包含。
答案 2 :(得分:1)
嗯,这很简单。您违反了单一责任原则(SPR),这是您的问题的原因。你的“引擎”课是上帝的课。精心设计的层次结构不会调用此问题。