11

私はクラスのダイヤモンド階層を持っています:

    A
  /   \
 B     C
  \   /
    D

D で A の 2 つのコピーを回避するには、B と C で仮想継承を使用する必要があります。

class A                     {  }; 
class B: virtual public A {};
class C: virtual public A   { }; 
class D: public B, public C { }; 

質問: あいまいさが D にあるのに、B と C で仮想継承を実行する必要があるのはなぜですか? Dにしたらもっと直感的だったのに。

この機能が標準化委員会によってこのように設計されているのはなぜですか? B クラスと C クラスがサードパーティ ライブラリから来ている場合、どうすればよいでしょうか?

編集: 私の答えは、派生オブジェクトが作成されるたびに A のコンストラクターを呼び出すべきではないことを B および C クラスに示すことでした。これは、D によって呼び出されるためです。

4

5 に答える 5

8

あいまいさが D にあるのに、B と C で仮想継承を実行する必要があるのはなぜですか? Dにしたらもっと直感的だったのに。

あなたの例では、B と C はvirtual、コンパイラに A のコピーが 1 つだけ含まれていることを確認するために具体的に使用しています。彼らがこれをしなかった場合、彼らは事実上、「独自の A 基底クラスが必要です。他の派生オブジェクトと共有するつもりはありません」と言っています。これは非常に重要です。

仮想基底クラスを共有したくない例

A が何らかのコンテナである場合、B はそこから派生し、特定のタイプのオブジェクトを格納します。たとえば、「Bat」を格納し、C は「Cat」を格納します。D が B と C が別々にコウモリとネコの個体群に関する情報を提供することを期待している場合、C の操作がコウモリに対して/一緒に何かをした場合、または B の操作が猫に対して/と何かをした場合、D は非常に驚くでしょう。

仮想基底クラスを共有したい例

D が A にあるいくつかの関数またはデータ メンバーへのアクセスを提供する必要があるとします。たとえば、「A::x」とします... A が B と C によって独立して (非仮想的に) 継承される場合、コンパイラは D を解決できません。 ::x を B::x または C::x に、プログラマが明示的に明確にする必要はありません。これは、派生チェーンによって暗示される「is-a」関係が 1 つではなく 2 つあるにもかかわらず、D を A として使用できないことを意味します (つまり、B "が"A であり、D" が "B" である場合、ユーザーはD が「A」であるかのように D を使用することを期待/必要とする)。

この機能が標準化委員会によってこのように設計されているのはなぜですか?

virtual継承が存在するのは、それが役立つ場合があるからです。これは、B と C の設計に関して侵入的な概念であり、B と C のカプセル化、メモリ レイアウト、構築と破棄、および関数のディスパッチにも影響するため、D ではなく B と C で指定されています。

B クラスと C クラスがサードパーティ ライブラリから来ている場合、どうすればよいでしょうか?

D が両方から継承し、A へのアクセスを提供する必要があるが、B と C が仮想継承を使用するように設計されておらず、変更できない場合、D は、A API に一致する要求を B と B のいずれかに転送する責任を負う必要があります。 /または C および/またはオプションで別の A から直接継承します (目に見える「is A」関係が必要な場合)。呼び出し元のコードが D を処理していることを認識している場合 (テンプレートを使用していても) は実用的ですが、基本クラスへのポインターを介したオブジェクトの操作では、D が実行しようとしている管理について認識されず、すべてが正しくするのは非常に難しいです。しかし、それは「ベクトルが必要で、リストしかない場合はどうなるか」、「ドライバーではなくノコギリ」と言っているようなものです...まあ、それを最大限に活用するか、本当に必要なものを手に入れてください.

編集: 私の答えは、派生オブジェクトが作成されるたびに A のコンストラクターを呼び出すべきではないことを B および C クラスに示すことでした。これは、D によって呼び出されるためです。

それはこれの重要な側面です、はい。

于 2011-03-02T11:51:36.077 に答える
8

彼らがこのように仮想継承を設計することを選択した正確な理由はわかりませんが、その理由はオブジェクトのレイアウトに関係していると思います。

C++ がひし形の問題を解決するように設計されているとします。B と C で A を仮想的に継承するのではなく、B と C を D で仮想的に継承します。誰もそれらから仮想的に継承しようとしない場合、それぞれが独自の A のコピーを持ち、B と C のそれぞれのベースに A がある標準の最適化されたレイアウトを使用できます。ただし、誰かB または C のいずれかから実質的に継承する場合、2 つは A のコピーを共有する必要があるため、オブジェクトのレイアウトは異なるものにする必要があります。

これの問題は、コンパイラが B と C を最初に見たときに、誰かがそれらを継承するかどうかを判断できないことです。したがって、コンパイラは、デフォルトでオンになっているより最適化されたバージョンの継承ではなく、仮想継承で使用される低速バージョンの継承にフォールバックする必要があります。これは、明示的に使用する言語機能に対してのみ料金を支払うという C++ の「使用していないものには料金を支払わない」(ゼロ オーバーヘッドの原則) という原則に違反しています。

于 2011-03-02T10:46:49.797 に答える
1

質問: あいまいさが D にあるのに、B と C で仮想継承を実行する必要があるのはなぜですか?

B および C のメソッドは、レイアウトが B および C 自身のレイアウトとは大きく異なるオブジェクトで動作する必要がある可能性があることを認識している必要があるためです。単一継承では、派生クラスは親の元のレイアウトの後に属性を追加するだけなので、問題はありません。

そもそも単一の親のレイアウトがないため、多重継承ではそれができません。さらに (A の重複を避けたい場合)、親のレイアウトは A の属性でオーバーラップする必要があります。C++ の多重継承は、かなりの複雑さを隠しています。

于 2011-03-02T12:19:50.443 に答える
0

Aは多重継承されたクラスであるため、仮想的に実行する必要があるのは、Aから直接派生したクラスです。

BとCの両方がAから派生し、Dで両方が必要で、ダイヤモンドを使用できない状況がある場合、DはBとCの一方のみから派生し、もう一方のインスタンスを持つことができます。関数を転送できます。

このような回避策:

class B : public A; // not your class, cannot change
class C : public A; // not your class, cannot change

class D : public B; // your class, implement the functions of B
class D2 : public C; // your class implement the functions of C

class D
{
   D2 d2;
};
于 2011-03-02T11:46:23.997 に答える