7

次のようにテストコードを作成しました。

#include <iostream>
using namespace std;

#ifndef interface
#define interface struct
#endif

interface Base
{
    virtual void funcBase() = 0;
};

interface Derived1 : public Base
{
    virtual void funcDerived1() = 0;
};

interface Derived2 : public Base
{
    virtual void funcDerived2() = 0;
};

interface DDerived : public Derived1, public Derived2
{
    virtual void funcDDerived() = 0;
};

class Implementation : public DDerived
{
public:
    void funcBase() { cout << "base" << endl; }
    void funcDerived1() { cout << "derived1" << endl; }
    void funcDerived2() { cout << "derived2" << endl; }
    void funcDDerived() { cout << "dderived" << endl; }
};

int main()
{
    DDerived *pObject = new Implementation;
    pObject->funcBase();

    return 0;
}

このコードを書いた理由は、関数 funcBase() が DDerived のインスタンスで呼び出せるかどうかをテストするためです。このコードをコンパイルしようとすると、C++ コンパイラ (Visual Studio 2010) でコンパイル エラー メッセージが表示されました。私の意見では、このコードに問題はありません。なぜなら、それは純粋な仮想であるためfuncBase()、インターフェイスの派生クラスで関数が実装される(したがってオーバーライドされる)ことが確実だからです。DDerivedつまり、型のポインター変数は、Implementation *Implentation を派生させて function をオーバーライドするクラスのインスタンスに関連付ける必要がありますfuncBase()

私の質問は、なぜコンパイラがそのようなエラーメッセージを表示するのですか? C++ 構文がそのように定義されている理由。つまり、このケースをエラーとして扱うには? コードを実行するにはどうすればよいですか? インターフェイスの多重継承を許可したい。もちろん、「仮想パブリック」を使用するか、関数funcBase()Implementation同様に再宣言すると

interface DDerived : public Derived1, public Derived2
{
    virtual void funcBase() = 0;
    virtual void funcDDerived() = 0;
};

その後、すべてが問題なく実行されます。

しかし、仮想継承はパフォーマンスを低下させる可能性があり、クラスの継承関係が非常に複雑な場合、再宣言は非常に面倒なので、それをしたくなくて、より便利な方法を探しています。仮想継承を使用する以外に、C++ でインターフェイスの複数の継承を有効にする方法はありますか?

4

4 に答える 4

7

定義すると、オブジェクト構造は次のようになります。

ここに画像の説明を入力してください

ここで重要な点は、の各インスタンスには、のImplementation2つの完全に別個のインスタンスが含まれていることですBase。のオーバーライドを提供していますが、を継承したのか、を介して継承したのBase::funcBaseかをオーバーライドしようとしているかどうかはわかりません。funcBaseBaseDerived1BaseDerived2

はい、これに対処するためのクリーンな方法は仮想継承です。これにより構造が変更されるため、Baseのインスタンスは1つだけになります。

ここに画像の説明を入力してください

これはほぼ間違いなくあなたが本当に望んでいることです。はい、プリミティブコンパイラや25MHz486などの時代のパフォーマンスの問題で評判を得ました。最新のコンパイラとプロセッサを使用すれば、問題が発生する可能性はほとんどありません。

別の可能性は、ある種のテンプレートベースの代替手段ですが、それはコードの残りの部分に浸透する傾向があります。つまり、を渡す代わりに、Base *関数A、B、およびCを提供するものすべてで機能するテンプレートを記述します。次にImplementation、テンプレートパラメータとして(と同等の)を渡します。

于 2012-04-27T15:25:16.760 に答える
3

C++ 言語は、仮想継承を使用しない最初のアプローチでは、メソッドの 2 つの親コピーが存在し、どちらを呼び出すべきか判断できないように設計されています。

仮想継承は、複数のベースから同じ関数を継承するための C++ ソリューションであるため、そのアプローチを使用することをお勧めします。

あるいは、複数のベースから同じ機能を継承しないことを検討しましたか? コンテキストに応じて、Derived1またはDerived2ORとして処理できるようにする必要がある派生クラスが本当にありますか?Base

この場合、不自然な例ではなく具体的​​な問題について詳しく説明することで、より良い設計を提供できる場合があります。

于 2012-04-27T15:16:34.767 に答える
1
DDerived *pObject = new Implementation;
pObject->funcBase();

これにより、実装へのDDerived型のポインタが作成されます。DDerivedを使用している場合、実際にはインターフェイスへのポインタがあります。

DDerivedは、FuncBaseがDerived1とDerived2の両方で定義されているというあいまいさのため、funcBaseの実装について知りません。

これにより、実際に問題を引き起こしている継承ダイヤモンドが作成されました。

http://en.wikipedia.org/wiki/Diamond_problem

私はあなたがそこに持っているインターフェース「キーワード」もチェックしなければなりませんでした

これは、VisualStudioによって認識されるms固有の拡張機能です。

于 2012-04-27T15:21:21.047 に答える
1

C++ Standard 10.1.4 - 10.1.5 は、コードの問題を理解するのに役立つと思います。

class L { public: int next; /∗ ... ∗/ };
class A : public L { /∗...∗/ };
class B : public L { /∗...∗/ };
class C : public A, public B { void f(); /∗ ... ∗/ };

10.1.4キーワード virtual を含まない基本クラス指定子は、非仮想基本クラスを指定します。キーワード virtual を含む基本クラス指定子は、仮想基本クラスを指定します。最も派生したクラスのクラス ラティスにおける非仮想基本クラスの個別の発生ごとに、最も派生したオブジェクト (1.8) には、その型の対応する個別の基本クラス サブオブジェクトが含まれます。virtual として指定された個別の基本クラスごとに、最も派生したオブジェクトには、その型の基本クラスのサブオブジェクトが 1 つ含まれます。【例: クラス型 C のオブジェクトの場合、C のクラス ラティス内の (非仮想) ベース クラス L の個別の出現は、型 C のオブジェクト内の個別の L サブオブジェクトと 1 対 1 で対応します。 クラス C が定義されている場合、上記では、クラス C のオブジェクトは、以下に示すようにクラス L の 2 つのサブオブジェクトを持ちます。

10.1.5そのようなラティスでは、明示的な修飾を使用して、どのサブオブジェクトが意味されているかを指定できます。関数 C::f の本体は、各 L サブオブジェクトのメンバ next を参照できます。 void C::f() { A::next = B::next; } //整形式。A:: または B:: 修飾子がないと、上記の C::f の定義はあいまいさのために不適切な形式になります。

したがって、 pObject->funcBase() を呼び出すときに修飾子を追加するか、別の方法であいまいさを解決してください。

pObject->Derived1::funcBase();

更新: 10.3 Virtual Functions of Standardも非常に役立ちます。

良い週末を :)

于 2012-04-27T15:31:48.210 に答える