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) */ }

と の両方が実質的RunnableEngineRenderingEngine拡張Engineされるため、ダイヤモンドの問題は影響しませんSimpleOpenGLRenderingEngine

ダイヤモンドの問題が発生したときに対処するのではなく、ダイヤモンドの問題に対して予防的なスタンスを取りたいと考えています。クラスを変更して、特定のクラス階層を作成できるようにします。たとえば、ボブがこれを行いたい場合:

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

そこで、純粋仮想クラスを常に仮想的に拡張するという慣習を使い始めたので、それらからの多重継承によってダイヤモンドの問題が発生することはありません。おそらくこれは、私が 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

簡単な答え:はい。あなたはそれを釘付けにしました。

長い答え:

そこで、常に純粋仮想クラスを拡張するという慣習を使い始めました

注: 適切な用語は抽象クラスであり、「純粋な仮想クラス」ではありません。

[抽象] クラスを常に仮想的に拡張するという慣例を使い始めたので、それらからの多重継承がひし形の問題を引き起こしません。

それはそう。これは適切な規則ですが、抽象クラスだけでなく、 interface を表すすべてのクラスに適用されます。これらのクラスの一部には、デフォルトの実装を持つ仮想関数があります。

(Java の欠点により、「インターフェイス」には純粋な仮想関数のみを配置し、データ メンバーを配置せずに済みます。通常どおり、C++ はより柔軟です。)

これにより、パフォーマンスに問題が発生する可能性があります

仮想性にはコストがかかります。

コードをプロファイリングします。

これにより、[] 機能などに問題が発生する可能性はありますか?

おそらく(まれに)。

基本クラスを非仮想化することはできません。特定の派生クラスが 2 つの別個の基本クラス サブオブジェクトを持つ必要がある場合、両方のブランチに継承を使用することはできず、包含が必要です。

于 2012-07-21T01:50:55.213 に答える
2

まあ、それはかなり簡単です。あなたは単一責任の原則 (SPR) に違反しており、これが問題の原因です。あなたの「エンジン」クラスは神のクラスです。適切に設計された階層では、この問題は発生しません。

于 2012-07-21T01:58:53.453 に答える