5

我经常使用纯虚拟类(接口)来减少当前项目中不同类的实现之间的依赖关系。我什至有层次结构,其中我有纯虚拟和非纯虚拟类扩展其他纯虚拟类,这对我来说并不罕见。下面是这种情况的一个例子:

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,使钻石问题不受影响。RenderingEngineEngineSimpleOpenGLRenderingEngine

我想对钻石问题采取预防性立场,而不是在它成为问题时处理它,特别是因为我喜欢编写尽可能容易让其他人使用的代码,我不希望他们不得不修改我的类,以便他们可以创建特定的类层次结构,例如,如果 Bob 想这样做:

class BobsRenderingEngine : public virtual RenderingEngine
{ /* Declares additional pure virtual methods only */ }

class BobsOpenGLRenderingEngine : public SimpleOpenGLRenderingEngine,
    public BobsRenderingEngine
{ /* Defines the methods declared in BobsRenderingEngine */ }

如果我没有进行虚拟SimpleOpenGLRenderingEngine扩展,这是不可能的。我知道 Bob 想要这样做的可能性可能非常低。RenderingEngine

所以,我已经开始使用总是虚拟地扩展纯虚拟类的约定,这样它们的多重继承就不会导致菱形问题。也许这是因为我来自 Java 并且倾向于只对非纯虚拟类使用单继承。我敢肯定在某些情况下这可能是矫枉过正,但使用这种约定有什么缺点吗?这会导致性能/功能等问题吗?如果不是,我看不出有理由不使用该约定,即使最终可能通常不需要它。

4

3 回答 3

3

我想说的是,一般来说,鼓励继承是一个坏主意(这就是您使用虚拟继承所做的事情)。实际上,很少有东西可以用继承所暗示的树结构很好地表示。此外,我发现多重继承往往会打破单一责任规则。我不知道您的确切用例,我同意有时我们别无选择。

然而,在 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
{ };
于 2012-03-04T01:04:34.263 回答
2

简短的回答:是的。你搞定了。

长答案:

所以,我开始使用总是扩展纯虚拟类的约定

注意:正确的术语是抽象类,而不是“纯虚拟类”。

我已经开始使用总是以虚拟方式扩展 [抽象] 类的约定,以便它们的多重继承不会导致菱形问题。

的确。这是一个合理的约定,但不仅适用于抽象类,还适用于所有代表接口的类,其中一些具有默认实现的虚函数。

(Java 的缺点迫使您只在“接口”中放置纯虚函数而没有数据成员,C++ 更灵活,就像往常一样。)

这是否会导致任何性能问题

虚拟性是有代价的。

分析您的代码。

这会导致 [] 功能等出现任何问题吗?

可能(很少)。

你不能取消虚拟化一个基类:如果某个特定的派生类需要有两个不同的基类子对象,你不能对两个分支都使用继承,你需要包含。

于 2012-07-21T01:50:55.213 回答
2

嗯,这很简单。您违反了单一责任原则 (SPR),这就是您的问题的原因。您的“引擎”课程是上帝课程。精心设计的层次结构不会引发这个问题。

于 2012-07-21T01:58:53.453 回答