18

次のコード:

struct interface_base
{
    virtual void foo() = 0;
};

struct interface : public interface_base
{
    virtual void bar() = 0;
};

struct implementation_base : public interface_base
{
    void foo();
};

struct implementation : public implementation_base, public interface
{   
    void bar();
};

int main()
{
    implementation x;
}

次のエラーでコンパイルに失敗します。

test.cpp: In function 'int main()':
test.cpp:23:20: error: cannot declare variable 'x' to be of abstract type 'implementation'
test.cpp:16:8: note:   because the following virtual functions are pure within 'implementation':
test.cpp:3:18: note:    virtual void interface_base::foo()

私はそれをいじってみて、「interface->interface_base」と「implementation_base->interface_base」の継承を仮想化すると問題が解決することを理解しましたが、理由はわかりません。誰かが何が起こっているのか説明してもらえますか?

psコードを短くするために、意図的に仮想デストラクタを省略しました。それらを入れるように私に言わないでください、私はすでに知っています:)

4

3 に答える 3

16

継承ツリーには2つの interface_base基本クラスがあります。これは、の2つの実装を提供する必要があることを意味しますfoo()。そして、それらのいずれかを呼び出すことは本当に厄介であり、曖昧さを解消するために複数のキャストが必要になります。これは通常、あなたが望むものではありません。

これを解決するには、仮想継承を使用します。

struct interface_base
{
    virtual void foo() = 0;
};

struct interface : virtual public interface_base
{
    virtual void bar() = 0;
};

struct implementation_base : virtual public interface_base
{
    void foo();
};

struct implementation : public implementation_base, virtual public interface
{   
    void bar();
};

int main()
{
    implementation x;
}

仮想継承では、問題の基本クラスの1つのインスタンスのみが、すべての仮想言及の継承階層に作成されます。したがって、でfoo()満たすことができるのは1つだけimplementation_base::foo()です。

詳細については、この前の質問を参照してください。回答は、これをより明確にするためのいくつかの優れた図を提供します。

于 2012-01-02T00:15:26.430 に答える
12

通常のC++イディオムは次のとおりです。

  • インターフェイスクラスのパブリック仮想継承
  • 実装クラスのプライベート非仮想継承

この場合、次のようになります。

struct interface_base
{
    virtual void foo() = 0;
};

struct interface : virtual public interface_base
{
    virtual void bar() = 0;
};

struct implementation_base : virtual public interface_base
{
    void foo();
};

struct implementation : private implementation_base,
                        virtual public interface
{   
    void bar();
};

implementation、一意のinterface_base仮想ベースは次のとおりです。

  • 公開継承interfaceimplementation-public-> interface--public->interface_base
  • プライベートに継承implementation_baseimplementation-private-> implementation_base--public->interface_base

クライアントコードがこれらの派生からベースへの変換のいずれかを実行する場合:

  • ベースポインタ変換から派生、
  • 派生した静的型の初期化子を使用した基本型の参照バインディング、
  • 派生静的型の左辺値を介した継承された基本クラスメンバーへのアクセス、

重要なのは、派生クラスから指定された基本クラスサブオブジェクトへのアクセス可能な継承パスが少なくとも1つあることだけです。他のアクセスできないパスは単に無視されます。基本クラスの継承はここでは仮想のみであるため、基本クラスのサブジェクトは1つだけであり、これらの変換があいまいになることはありません。

implementationここで、からへ の変換は、常に;interface_baseを介してクライアントコードで実行できます。interface他のアクセスできないパスはまったく問題ではありません。一意のinterface_base仮想ベースは、からパブリックに継承されimplementationます。

多くの場合、実装クラス(implementation、 )はクライアントコードから隠されたままになります。インターフェイスクラス( 、 )implementation_baseへのポインタまたは参照のみが公開されます。interfaceinterface_base

于 2012-08-01T20:39:26.530 に答える
2

ダイヤモンドの継承問題を「解決」する場合は、bdonlan が提供する解決策が有効です。そうは言っても、ダイヤモンドの問題はデザインで回避できます。特定のクラスのすべてのインスタンスが両方のクラスとして見なされなければならないのはなぜですか? この同じオブジェクトを、次のようなクラスに渡すことはありますか?

void ConsumeFood(Food *food);
void ConsumeDrink(Drink *drink);

class NutritionalConsumable {
  float calories() = 0;
  float GetNutritionalValue(NUTRITION_ID nutrition) = 0;
};
class Drink : public NutritionalConsumable {
  void Sip() = 0;
};
class Food : public NutritionalConsumable {
  void Chew() = 0;
};
class Icecream : public Drink, virtual public Food {};

void ConsumeNutrition(NutritionalConsumable *consumable) {
  ConsumeFood(dynamic_cast<Food*>(food));
  ConsumeDrink(dynamic_cast<Drink*>(drink));
}

// Or moreso
void ConsumeIcecream(Icecream *icecream) {
  ConsumeDrink(icecream);
  ConsumeFood(icecream);
}

確かに、この場合、純粋に食べ物または飲み物として表示するために、プロキシを返すandメソッドをIcecream実装NutritionalConsumableして提供する方が良いでしょう。それ以外の場合は、 を受け入れるメソッドまたはオブジェクトがあることを示唆していますが、何らかの方法で後でそれを として見たいと考えています。これは でのみ実現でき、より適切な設計ではそうである必要はありません。GetAsDrink()GetAsFood()FoodDrinkdynamic_cast

于 2012-01-02T00:41:15.817 に答える