11

このC++コードを考えてみましょう。

#include <iostream>
using namespace std;

struct B {
    virtual int f() { return 1; }
    int g() { return 2; }
};
struct D1 : public B { // (*)
    int g() { return 3; }
};
struct D2 : public B { // (*)
    virtual int f() { return 4; }
};
struct M : public D1, public D2 {
    int g() { return 5; }
};

int main() {
    M m;
    D1* d1 = &m;
    cout << d1->f()
         << static_cast<D2&>(m).g()
         << static_cast<B*>(d1)->g()
         << m.g();
}

印刷し1225ます。仮想継承を作成する場合、つまり(*)でマークされた行にvirtual前に追加すると、が出力されます。public4225

  1. 1に変更する理由を説明できます4か?
  2. static_cast<D2&>(m)との意味を説明していただけますstatic_cast<B*>(d1)か?
  3. このような組み合わせで迷子にならないようにしていますか?何か描いていますか?
  4. 通常のプロジェクトでこのような複雑な設定を見つけるのは一般的ですか?
4

5 に答える 5

5

写真は言葉よりも雄弁なので、答えの前に...


D1およびD2のBの仮想ベース継承のないクラスM階層

    M
   / \
  D1 D2
  |   |
  B   B

D1およびD2のBの仮想ベース継承を使用したクラスM階層:

    M
   / \
  D1 D2
   \ /
    B

  1. クロスデリゲーション、または私がそれを呼ぶのが好きなように、ひねりを加えた兄弟ポリモーフィズム。仮想ベースの継承により、B :: f()オーバーライドがD2:f()に修正されます。仮想関数が実装されている場所と、継承チェーンの結果として仮想関数が何をオーバーライドするかを検討するときに、この図がこれを説明するのに役立つことを願っています。

  2. この場合のstatic_cast演算子の使用により、派生クラスから基本クラスへの変換が促進されます。

  3. 本当に悪いコードを読んで、言語の基盤がどのように機能するかを知っている多くの経験

  4. ありがたいことにいいえ。それは一般的ではありません。ただし、これがまったく混乱している場合は、元のiostreamライブラリは悪夢を与えていたでしょう。

于 2012-11-11T17:45:39.433 に答える
4

1が4に変わる理由を説明できますか?

なぜに変わるの4ですか?相互委任のため。

仮想継承前の継承グラフは次のとおりです。

B   B
|   |
D1  D2
 \ /
  M

d1はであるD1ため、存在することD2すらわかりません。また、その親(B)には存在することもありませんD2。考えられる唯一の結果は、B::f()と呼ばれることです。

仮想継承が追加された後、基本クラスは一緒にマージされます。

  B
 / \
D1  D2
 \ /
  M

ここで、を要求d1するとf()、その親に見えます。今、それらは同じを共有しているBので、B'sf()はオーバーライドされD2::f()、あなたはを取得します4

はい、これは奇妙なことです。それは、何も知らないD1から関数を呼び出すことができたことを意味します。D2これはC++のより奇妙な部分の1つであり、通常は回避されます。


static_cast(m)とstatic_cast(d1)の意味を説明できますか?

何が分かりませんか?m彼らはそれぞれとd1にキャストD2&B*ます。


このような組み合わせで迷子にならないようにしていますか?何か描いていますか?

この場合ではありません。それは複雑ですが、頭の中にとどまるのに十分小さいです。上記の例では、できるだけ明確にするためにグラフを描いています。


通常のプロジェクトでこのような複雑な設定を見つけるのは一般的ですか?

いいえ。それは単純に複雑すぎるため、誰もが恐ろしい遺伝形式のダイアモンドを避けることを知っています。通常、やりたいことをもっと簡単に行う方法があります。

一般に、継承よりも合成を優先する方が適切です。

于 2012-11-11T17:41:40.197 に答える
2

(1)1が4に変わる理由を説明してください。

継承がないvirtual場合、継承には2つの独立した階層があります。B->D1->MおよびB->D2->Mvirtualしたがって、2つの関数テーブルを想像してください(これは実装で定義されていますが)。
で呼び出すf()D1*、それはただ知っているだけで、それだけB::f()です。virtual継承を使用すると、baseはclass Bに委任されるためMD2::f()の一部と見なされclass Mます。

static_cast<D2&>(m)(2)との意味を説明していただけますstatic_cast<B*>(d1)か?

static_cast<D2&>(m)は、のオブジェクトをclass Masと見なすclass D2
static_cast<B*>(d1)ようなものであり、のポインタをclass D1asと見なすようなものclass B1です。
どちらも有効なキャストです。ではないので
、関数の選択はコンパイル時に行われます。それなら、これらすべてのキャスティングは重要ではありません。g()virtualvirtual

(3)このような組み合わせで迷子にならないようにしていますか?何か描いていますか?

もちろん、それは複雑で、一見、そのようなクラスが非常に多いと、簡単に迷子になる可能性があります。

(4)通常のプロジェクトでは、このような複雑な設定を見つけるのが一般的ですか?

まったくそうではありませんが、それは珍しいことであり、時にはコードの臭いがします。

于 2012-11-11T17:50:40.357 に答える
2

この質問は実際には複数の質問です。

  1. 非継承が使用されている場合、virtual関数B::f()がオーバーライドされないのはなぜですか?virtual答えは、もちろん、2つのBaseオブジェクトがあるということです。1つはベースがD1オーバーライドf()され、もう1つはベースがD2オーバーライドされませんf()。を呼び出すときにオブジェクトを派生させると見なすブランチに応じて、f()異なる結果が得られます。サブオブジェクトが1つだけになるように設定を変更するとB、継承グラフ内のオーバーライドが考慮されます(両方のブランチがオーバーライドする場合、ブランチが再度マージされる場所でオーバーライドしない限り、エラーが発生すると思います。
  2. どういうstatic_cast<D2&>(m)意味ですか?f()からのバージョンは2つBaseあるため、どちらかを選択する必要があります。static_cast<D2&>(m)あなたと一緒にオブジェクトとして表示しMますD2。キャストがないと、コンパイラーは2つのサブジェクトのどちらを見ているのかを判断できず、あいまいなエラーが発生します。
  3. どういうstatic_cast<B*>(d1)意味ですか?たまたま不要ですが、オブジェクトをオブジェクトとしてB*のみ表示します。

一般的に、私は些細なことではないものに対して多重継承を避ける傾向があります。ほとんどの場合、私は多重継承を使用して、空のベースの最適化を利用したり、メンバーの数が可変の何かを作成したりしています(think std::tuple<...>)。本番コードのポリモーフィズムを処理するために多重継承を使用する実際の必要性に遭遇したことがあるかどうかはわかりません。

于 2012-11-11T17:40:44.060 に答える
2

1)1が4に変わる理由を説明できますか?

仮想継承がない場合、この「ダイアモンド」の各ブランチに1つずつ、合計2つのインスタンスがあります。ダイアモンドエッジの1つ()は関数をオーバーライドし、もう1つ()はオーバーライドしません。として宣言されているので、関数がオーバーライドされていないコピーにアクセスしたいことを意味します。にキャストすると、異なる結果が得られます。BMD2D1d1D1d1->f()BD2

B仮想継承を使用すると、の2つのインスタンスを1つにマージするため、が作成されるとD2::f効果的にオーバーライドされます。B:fM

static_cast<D2&>(m)2)との意味を説明していただけますstatic_cast<B*>(d1)か?

D2&彼らはそれぞれとにキャストしB*ました。gは仮想ではないため、がB:::g呼び出されます。

3)このような組み合わせで迷子にならないようにしていますか?何か描いていますか?

時々 ;)

4)通常のプロジェクトでは、このような複雑な設定を見つけるのが一般的ですか?

あまり一般的ではありません。実際、仮想継承はもちろんのこと、複数の言語がなくても問題なく動作する言語があります(Java、C#...)。

ただし、特にライブラリ開発では、作業が簡単になる場合があります。

于 2012-11-11T17:55:12.483 に答える