12

以下のコードで、コンパイラPureAbstractBaseが のあいまいな基本クラスであると不平を言うのはなぜMultiplyInheritedClassですか? PureAbstractBaseinとMultiplyInheritedClassthatの 2 つのコピーがあり、それらがひし形の中央の行であるため、仮想的に派生する必要があることに気付きました (実際、以下のコードの問題を解決しています)。しかし、インターフェイスのコピーが 2 つあるにもかかわらず、 のコードが両方をオーバーライドして、 で定義されたインターフェイス クラスを明確に選択しないのはなぜですか?FirstConreteClassSecondConreteClassMultiplyInheritedClassMultiplyInheritedClass

#include <iostream>
using namespace std;

class PureAbstractBase {
  public:
    virtual void interface() = 0;
};

// I know that changing the following line to:
// class FirstConcreteClass : public virtual PureAbstractBase {
// fixes the problem with this hierarchy
class FirstConcreteClass : public PureAbstractBase {
  public:
    virtual void interface() { implementation(); }
  private:
    void implementation() { cout << "This is object FirstConcreteClass\n"; }
};

// I know that changing the following line to:
// class SecondConcreteClass : public virtual PureAbstractBase {
// fixes the problem with this hierarchy
class SecondConcreteClass : public PureAbstractBase {
  public:
    virtual void interface() { implementation(); }
  private:
    void implementation() { cout << "This is object SecondConcreteClass\n"; }
};

class MultiplyInheritedClass : public FirstConcreteClass,
                               public SecondConcreteClass {
  public:
    virtual void interface() { implementation(); }
  private:
    void implementation() { cout << "This is object MultiplyInheritedClass\n"; }
};

さらに、次の階層で問題が発生しないのはなぜですか? この場合、ConcreteHandler クラスには AbstractTaggingInterface のコピーが 3 つありませんか? では、なぜ上記の例と同じ問題が発生しないのでしょうか?

#include <iostream>
using namespace std;

class AbstractTaggingInterface {
  public:
    virtual void taggingInterface() = 0;
};

class FirstAbstractHandler : public AbstractTaggingInterface {
  public:
    virtual void taggingInterface() { cout << "FirstAbstractHandler\n"; }
    virtual void handleFirst() = 0;
};

class SecondAbstractHandler : public AbstractTaggingInterface {
  public:
    virtual void taggingInterface() { cout << "SecondAbstractHandler\n"; }
    virtual void handleSecond() = 0;
};

class ThirdAbstractHandler : public AbstractTaggingInterface {
  public:
    virtual void taggingInterface() { cout << "ThridAbstractHandler\n"; }
    virtual void handleThird() = 0;
};

class ConcreteHandler : public FirstAbstractHandler,
                        public SecondAbstractHandler,
                        public ThirdAbstractHandler {
  public:
    virtual void taggingInterface() = { cout << "ConcreteHandler\n"; }
    virtual void handleFirst() {}
    virtual void handleSecond() {}
    virtual void handleThird() {}
};

最近同僚と話をしたところ、データ メンバーなしで純粋な仮想クラス (インターフェイス) から継承する場合、仮想継承は必要ないと主張したため、これらすべてについて頭を悩ませようとしています。前者のコード例が機能せず、後者が機能する理由を理解することは、これを頭の中で理解するのに大いに役立つと思います(そして、彼のコメントが正確に何を意味していたのかを明確にします)。前もって感謝します。

4

4 に答える 4

5

最初の例は失敗します。これは、コンパイラが の 3 つの実装を明確に区別できないためですimplementation()。でそのメソッドをオーバーライドしていMultiplyInheritedClassます。これは、実際には と の両方FirstConcreteClass::implementationをオーバーライドしますSecondConcreteClass::implementation(一度仮想化すると、常に仮想化されます)。ただし、どちらの仮想コールも のインターフェイスに存在するためMultiplyInheritedClass、コール サイトでのコールがあいまいになります。

あなたの例がvirtual継承なしで機能する理由は、共通の基本クラスの競合する実装がないためです。別の言い方をすれば:

class Base
{
public:
    void DoSomething() {
    std::cout << "TADA!";
    }
}

class One : public Base
{
    //...
}

class Two : public Base
{
    //...
}

class Mixed : public One, public Two
{
    //...
}

int main()
{
    Mixed abc;
    abc.DoSomething(); //Fails because the compiler doesn't know whether to call
                       // One::DoSomething or Two::DoSomething, because they both
                       // have implementations.

    //In response to comment:
    abc.One::DoSomething(); //Succeeds! You removed the ambiguity.
}

あなたの例にはすべての純粋な仮想関数があるため、コンパイラが明確にする必要がある複数の実装はありません。したがって、実装は 1 つだけ存在し、呼び出しは明確です。

于 2011-06-19T17:32:59.933 に答える
5

ダイヤモンドのあいまいさを克服するには、仮想継承が必要です。

class FirstConcreteClass  : public virtual PureAbstractBase { ... };
class SecondConcreteClass : public virtual PureAbstractBase { ... };

長い説明:これがあるとします:

// *** Example with errrors! *** //
struct A { virtual int foo(); };
struct B1 : public A { virtual int foo(); };
struct B2 : public A { virtual int foo(); };
struct C: public B1, public B2 { /* ... */ };  // ambiguous base class A!

int main() {
  A * px = new C;                              // error, ambiguous base!
  px->foo();                                   // error, ambiguous override!
}

仮想関数の継承は、 from 、 from 、および fromfooの 3 つの方法があるため、あいまいです。継承図は「ひし形」を形成します。B1B2A

   /-> B1 >-\
A->          ->C
   \-> B2 >-/

継承を仮想化するstruct B1 : public virtual A;などして、の基本クラスがC*正しいメンバーを呼び出せるようにします。

struct A { virtual int foo(); };
struct B1 : public virtual A { virtual int foo(); };
struct B2 : public virtual A { virtual int foo(); };
struct C: public B1, public B2 { virtual int foo(); };

そうしないと明確に定義された member がないため、これを意味のあるものにするためにも定義する必要があります。C::foo()Cfoo

詳細: 上記のように適切に仮想継承するクラスCがあるとします。必要に応じて、さまざまな仮想メンバーすべてにアクセスできます。

int main() {
  A * pa = new C;
  pa->foo();      // the most derived one
  pa->A::foo();   // the original A's foo

  B1 * pb1 = new C;
  pb1->foo();     // the most derived one
  pb1->A::foo();  // A's foo
  pb1->B1::foo(); // B1's foo

  C * pc = new C;
  pc->foo();      // the most derived one
  pc->A::foo();   // A's foo
  pc->B1::foo();  // B1's foo
  pc->B2::foo();  // B2's foo
  pc->C::foo();   // C's foo, same as "pc->foo()"
}

 

更新: David がコメントで述べているように、ここで重要な点は、中間クラスB1B2継承が仮想的に継承されるため、継承を明確に保ちながら、さらにクラス (この場合Cは ) を継承できることです。初歩的なミスすみません、訂正ありがとうございます!A

于 2011-06-19T17:14:05.567 に答える
1

両方の質問コードを試してみたところ、多重継承クラスのオブジェクトをインスタンス化するときに問題なく機能しました。たとえば、次のように、ポリモーフィズムだけでは機能しませんでした。

PureAbstractBase* F;
F = new MultiplyInheritedClass();

その理由は明らかです。Abstract 基底クラスのどのコピーにリンクする必要があるのか​​ わかりません(表現が悪くて申し訳ありません。アイデアは理解できますが、表現できません)。また、仮想的に継承すると、派生クラスにコピーが 1 つだけ存在するため、問題ありません。

また、Billy ONeal のコードがまったくわかりません。コメントの代わりに何を配置すればよいでしょうか?

配置すると:

public:    
void DoSomething() 
{    std::cout << "TADA!";    }

仮想性がないため、正常に動作します。

Visual Studio 2008 で作業しています。

于 2011-06-19T18:43:24.607 に答える
0

このようにしてみませんか ( Benjamin Supnik のブログ エントリで提案されています):

#include <iostream>

class PureAbstractBase {
public:
    virtual void interface() = 0;
};

class FirstConcreteClass : public PureAbstractBase {
public:
    virtual void interface() { implementation(); }
private:
    void implementation() { std::cout << "Fisrt" << std::endl; }
};

class SecondConcreteClass : public PureAbstractBase {
public:
    virtual void interface() { implementation(); }
private:
    void implementation() { std::cout << "Second" << std::endl; }
};

class MultiplyInheritedClass : public FirstConcreteClass,
                               public SecondConcreteClass 
{
public:
    virtual void interface() { implementation(); }
private:
    void implementation() { std::cout << "Multiple" << std::endl; }
};

int main() {
MultiplyInheritedClass mic;
mic.interface();

FirstConcreteClass *fc = &mic; //disambiguate to FirstConcreteClass 
PureAbstractBase *pab1 = fc;
pab1->interface();

SecondConcreteClass *sc = &mic; //disambiguate to SecondConcreteClass 
PureAbstractBase *pab2 = sc;
pab2->interface();
}

与える:

Multiple
Multiple
Multiple    

こちらです:

  • 仮想ベースは関係ありません (本当に必要ですか?)
  • のインスタンスを介してオーバーライドされた関数を呼び出すことができますMultiplyInheritedClass
  • 二段階変換で曖昧さを解消
于 2012-01-23T13:08:31.637 に答える