14

C++ の仮想継承の基本を理解しています。virtualただし、複雑なクラス階層でキーワードを使用する必要がある正確な場所については混乱しています。たとえば、次のクラスがあるとします。

            A
           / \
          B   C
         / \ / \
        D   E   F
         \ / \ /
          G   H
           \ /
            I

どのクラスもどのサブクラスにも複数回出現しないようにしたい場合、どの基本クラスをマークする必要がありvirtualますか? それらのすべて?または、そうでなければ複数のインスタンスを持つ可能性のあるクラスから直接派生するクラス (つまり、B、C、D、E、F、および G と H) でのみ使用するだけで十分ですか?基本クラス D および F))?

4

7 に答える 7

26

仮想基地の複雑さを研究するのに役立つプログラムを一緒におもちゃにしました. クラス階層を、graphiviz ( http://www.graphviz.org/I )に適したダイグラフとして出力します。各インスタンスには、構築順序を理解するのにも役立つカウンターがあります。プログラムは次のとおりです。

#include <stdio.h>
int counter=0; 



#define CONN2(N,X,Y)\
    int id; N() { id=counter++; }\
    void conn() \
    {\
        printf("%s_%d->%s_%d\n",#N,this->id,#X,((X*)this)->id); \
        printf("%s_%d->%s_%d\n",#N,this->id,#Y,((Y*)this)->id); \
        X::conn(); \
        Y::conn();\
    }
#define CONN1(N,X)\
    int id; N() { id=counter++; }\
    void conn() \
    {\
        printf("%s_%d->%s_%d\n",#N,this->id,#X,((X*)this)->id); \
        X::conn(); \
    }

struct A { int id; A() { id=counter++; } void conn() {} };
struct B : A { CONN1(B,A) };
struct C : A { CONN1(C,A)  };
struct D : B { CONN1(D,B) };
struct E : B,C { CONN2(E,B,C) };
struct F : C { CONN1(F,C) };
struct G : D,E { CONN2(G,D,E) };
struct H : E,F { CONN2(H,E,F) };
struct I : G,H { CONN2(I,G,H) };
int main()
{
    printf("digraph inh {\n");
    I i; 
    i.conn(); 
    printf("}\n");
}

これを実行すると ( g++ base.cc ; ./a.out >h.dot ; dot -Tpng -o o.png h.dot ; display o.png)、典型的な非仮想ベース ツリーが得られます。 代替テキスト

十分な仮想性を追加しています...

struct B : virtual A { CONN1(B,A) };
struct C : virtual A { CONN1(C,A)  };
struct D : virtual B { CONN1(D,B) };
struct E : virtual B, virtual C { CONN2(E,B,C) };
struct F : virtual C { CONN1(F,C) };
struct G : D, virtual E { CONN2(G,D,E) };
struct H : virtual E,F { CONN2(H,E,F) };
struct I : G,H { CONN2(I,G,H) };

..結果はひし形になります (数字を見て、組み立て順序を確認してください!!)

代替テキスト

しかし、すべてのベースを仮想化すると:

struct A { int id; A() { id=counter++; } void conn() {} };
struct B : virtual A { CONN1(B,A) };
struct C : virtual A { CONN1(C,A)  };
struct D : virtual B { CONN1(D,B) };
struct E : virtual B, virtual C { CONN2(E,B,C) };
struct F : virtual C { CONN1(F,C) };
struct G : virtual D, virtual E { CONN2(G,D,E) };
struct H : virtual E, virtual F { CONN2(H,E,F) };
struct I : virtual G,virtual H { CONN2(I,G,H) };

初期化順序が異なるダイヤモンドを取得します。

代替テキスト

楽しむ!

于 2010-08-05T13:05:36.827 に答える
8

virtualA、B、C、および E クラス (ひし形の上部にある) のいずれかから継承する場合は、継承を指定する必要があります。

class A;
class B: virtual A;
class C: virtual A;
class D: virtual B;
class E: virtual B, virtual C;
class F: virtual C;
class G:         D, virtual E;
class H: virtual E,         F;
class I:         G,         H;
于 2010-08-05T12:34:24.567 に答える
2

私の個人的な提案は、 B と C : virtual A から始めて、コンパイラが文句を言わなくなるまで追加し続けることです。

実際には、B と C は仮想 A、G と H は仮想 E、E は仮想 B と C と言えます。他のすべての継承リンクは通常の継承である可能性があります。ただし、この怪物は仮想電話をかけるのに60年ほどかかるでしょう.

于 2010-08-05T12:32:42.223 に答える
1

階層内の最上位クラスのオブジェクト (この場合) に各親クラスのサブオブジェクトが 1 つだけ含まれていることを確認したい場合は、階層内で複数のスーパークラスIを持つすべてのクラスを見つけて、これらのクラスを仮想化する必要があります。スーパークラスのベース。それでおしまい。

あなたの場合、クラスABCおよびEは、この階層で継承するたびに仮想基本クラスになる必要があります。

クラスDFGおよびは、H仮想基底クラスになる必要はありません。

于 2010-08-05T15:01:50.403 に答える
0

編集: A が最も派生したクラスだと思いました ;)

@Lutherの答えは本当にクールですが、元の質問に戻ります:

virtual継承階層で少なくとも 1 つの他のクラスが継承するクラスから継承する場合は、継承を使用する必要があります (Luther の図では、少なくとも 2 つの矢印がクラスを指していることを意味します)。

Dここでは、 , F, Gandの前には不要です。これらから派生するクラスは 1 つだけだからです (現時点ではH派生クラスはありません)。I

ただし、別のクラスが基本クラスから継承されるかどうかが事前にわからない場合はvirtual、予防策として追加できます。たとえば、Stroustrup 自身がExceptionクラスを仮想的に継承することをお勧めします。std::exception

Luther が指摘したように、それはインスタンス化の順序を変更します (そしてパフォーマンスにわずかな影響を与えます) が、私は最初から構築順序に依存する設計は間違っていると考えています。精度と同様に、派生クラスの属性の前、つまり派生クラスのコンストラクター本体の実行前に、基本クラスが初期化されるという保証がまだあります。

于 2010-08-05T15:43:11.127 に答える
0

留意すべきことは、C++ が継承のテーブルを保持していることです。仮想クラスを追加すればするほど、コンパイル時間 (リンケージ) が長くなり、ランタイムが重くなります。

一般に、仮想クラスを回避できる場合は、いくつかのテンプレートに置き換えるか、何らかの方法で分離を試みることができます。

于 2015-10-29T14:08:46.490 に答える
0

各タイプのインスタンスごとに、各タイプの「物理的」インスタンスを 1 つだけ持つ場合 (A を 1 つだけ、B を 1 つだけなど)、継承を使用するたびに仮想継承を使用する必要があります。

いずれかのタイプの個別のインスタンスが必要な場合は、通常の継承を使用してください。

于 2010-08-05T12:36:34.203 に答える