3

仮想継承に取り組むときにこの問題が発生します。非仮想継承階層では、サブクラスのオブジェクトがその直接のスーパークラスのオブジェクトを保持していることを覚えています。仮想継承についてはどうですか?この状況では、サブクラスのオブジェクトはそのスーパークラスのオブジェクトを直接保持しますか、それとも単にそのスーパークラスのオブジェクトを指すポインターを保持しますか?

ちなみに、なぜ次のコードの出力は次のとおりです。

sizeof(A): 8
sizeof(B): 20
sizeof(C): 20
sizeof(D): 36

コード:

#include <iostream>

using namespace std;

class A{
    char k[ 3 ];
    public:
        virtual void a(){};
};

class B : public virtual A{
    char j[ 3 ];
    public:
        virtual  void b(){};
};

class C : public virtual A{
    char i[ 3 ];
    public:
        virtual void c(){};
};

class D : public B, public C{
    char h[ 3 ];
    public:
        virtual void d(){};
};

int main( int argc, char *argv[] ){
    cout << "sizeof(A): " << sizeof( A ) << endl;
    cout << "sizeof(B): " << sizeof( B ) << endl;
    cout << "sizeof(C): " << sizeof( C ) << endl;
    cout << "sizeof(D): " << sizeof( D ) << endl;

    return 0;
}

前もって感謝します。敬具。

4

5 に答える 5

5

仮想ベースオブジェクトは、オブジェクトに属するメモリブロックのどこかにあります(size = sizeof(object)のメモリ)。異なるタイプの複数のサブオブジェクトはさまざまな方法で組み合わせることができますが、同じベースオブジェクトを共有する必要があるため、仮想ベースオブジェクトを見つけるには、各サブオブジェクトにオフセットポインタが必要です。仮想継承がない場合、対応するベースオブジェクトを見つけるためのオフセットは、各クラスタイプのコンパイル時に固定されます。

sizeof値はコンパイラとマシンによって異なりますが、次の仮定は非常に一般的です。

仮定:ポインタサイズは4バイトです

仮定:クラスサイズは4バイトの倍数に切り上げられます

sizeof(A): 8  ->   1 pointer to vtable (virtual method) 
                 + 3 chars -> 4+3=7 
              -> round up to 8

sizeof(B): 20 ->   8 + 1 pointer to vtable (virtual method) 
                 + 1 offset pointer to virtual base 
                 + 3 chars -> 8 + 4 + 4 + 3 = 19 
              -> round up to 20

sizeof(C): 32 ->  20 + 1 pointer to vtable (virtual method) 
                 + 1 offset pointer to virtual base 
                 + 3 chars 
              -> 20 + 4 + 4 + 3 = 31 // this calculation refers to an older 
              -> round up to 32      // version of the question's example 
                                     // where C had B as base class

実際の計算はコンパイラがどのように機能するかを正確に知っている必要があるため、計算は推測されます。

よろしく、オリバー

追加のオフセットポインタが必要な理由の詳細:

例:

class B  : virtual public A {...};
class C  : virtual public A {...};
class D1 : public B {...};
class D2 : public B, C {...};

D1の可能なメモリレイアウト。

A
B
D1

D2の可能なメモリレイアウト:

A
C
B
D2

2番目のケースでは、サブオブジェクトBは、そのベースAを見つけるために別のオフセットが必要です。

タイプD2のオブジェクトは、すべての親オブジェクト部分が含まれるメモリブロックで構成されます。つまり、タイプD2のオブジェクトのメモリブロックには、Aメンバー変数、Cメンバー変数、Bメンバー変数、およびD2メンバー変数。これらのセクションの順序はコンパイラに依存しますが、この例は、複数の仮想継承の場合、オブジェクトの合計メモリブロック内 で仮想ベースオブジェクトを指すオフセットポインタが必要であることを示しています。これが必要なのは、クラスBのメソッドがBへのこのポインターを1つだけ知っており、Aメモリ部分がこのポインターに対してどこにあるかを何らかの方法で計算する必要があるためです。

(D)の計算サイズ:

sizeof(D): 36 ->   A:3 chars + A:vtable 
                 + B:3 chars + B:vtable + B:virtual base pointer
                 + C:3 chars + C:vtable + C:virtual base pointer
                 + D:3 chars + D:vtable
               =   3 + 4 
                 + 3 + 4 + 4 
                 + 3 + 4 + 4 
                 + 3 + 4 
                 = 36

上記の計算はおそらく間違っています;-)..。

D部分に独自のvtableポインターがあるかどうかはわかりません(これはすべてコンパイラーに大きく依存します)。

D部分がその親クラスのvtableポインターエントリを使用し、各部分の整列に4バイトの追加バイトが使用されている可能性があると思います(8バイトの倍数)。

したがって、この計算はおそらくより正確です。

sizeof(D): 36 ->   A:3 chars + A:vtable + A:alignment
                 + B:3 chars + B:vtable + B:virtual base pointer + B:alignment
                 + C:3 chars + C:vtable + C:virtual base pointer + C:alignment
                 + D:3 chars + D:alignment
               =   3 + 4 + 1
                 + 3 + 4 + 4 + 1 
                 + 3 + 4 + 4 + 1
                 + 3 + 1
                 = 36
于 2010-05-02T17:17:00.410 に答える
1

上記の質問には3点分析があります

a。仮想継承

「仮想継承は、クラスが仮想基本クラスの状態を共有する意思があることを指定するメカニズムです。仮想継承では、クラスの回数に関係なく、特定の仮想ベースに対して1つの共有基本クラスサブオブジェクトのみが継承されます。派生階層内の仮想ベースとして発生します。共有ベースクラスサブオブジェクトは、仮想ベースクラスと呼ばれます。」...リップマンから

仮想継承は、多重継承から継承された重複サブオブジェクトのみを回避します。ただし、これは、基本クラスのオブジェクトがサブオブジェクトではないことを示すものではありません。それどころか、仮想継承中であっても、サブオブジェクト(少なくとも1つのコピーが存在します-つまり、sizeof()操作に含まれます)。

b。仮想関数

仮想関数は、階層に関与するオブジェクトのメンバー関数を動的にバインドするためのものです。したがって、これでもサブオブジェクトの配置には意味がありません。

c。サブオブジェクトの実装

これは完全にコンパイラに依存しており、すべての理由から、その実装で判断するのは非常に困難です。ただし、オブジェクトのsizeof()に基本クラス(sub)オブジェクトのサイズも含まれることを確認できます。また、基本クラスオブジェクトが埋め込まれているものとして視覚化できます。

継承された関数の各オブジェクトには、サブオブジェクト用のスペースが確実に含まれます。

HTH

于 2010-05-02T17:34:19.583 に答える
1

サブクラスのオブジェクトは、そのスーパークラスのオブジェクトを直接保持しますか

はい、それは継承が仮想であるかどうかにかかわらずそれがどのように機能するかです。ただし、「含む」と「保持する」という言葉を使用します。

階層が次のようになり、仮想継承がどこにもない場合:

#     W    <--- base class
#    / \
#   X   Y  <--- subclasses of W
#    \ /
#     Z    <--- most derived class

次にZ、のコピーが2つありWます。ただし、X-->WY-->W継承を仮想化すると、の2つのスーパークラスが共通の基本クラスを共有するため、のZコピーは1つだけになります。WZ

#     W
#    / \   <--- make these two virtual to eliminate duplicate W in Z.
#   X   Y
#    \ /
#     Z

あなたの例では:

class A{...};
class B : public virtual A{...};
class C : public virtual B{...}; // Edit: OP's code had this typo when I answered
class D : public B, public C{...};

Bに実質的にAから継承させる必要はありません。必要な仮想継承はC-->BとだけですD-->B。これは、ダイヤモンドが継承階層を上って「マージ」する場所だからです。

#   What you have     |     What you want?
#             A       |               A
#            /        |              /
#           /v        |             /
#          /          |            /
#         B           |           B
#        / \          |          / \
#       /v  \         |         /v  \v
#      /     \        |        /     \
#     C       )       |       C       )
#      \     /        |        \     /
#       \   /         |         \   /
#        \ /          |          \ /
#         D           |           D

もちろん、BだけでなくAから継承する、表示されていない他のクラスがある場合、それは状況を変えます-おそらく、B-->Aあなたが私たちに話していない別のダイヤモンドがある場合、継承は仮想である必要があります。

于 2010-05-03T09:31:53.833 に答える
0

非仮想継承階層では、サブクラスのオブジェクトがその直接のスーパークラスのオブジェクトを保持していることを覚えています。
それは正しくありません。いくつかの実装がこのようにそれを行う予定ですが、標準C++ではそのように定義されていません。標準C++は、これらの実装方法を指定していません。

仮想継承は、派生クラスが共通の基本クラスから継承する2つの基本クラスから多重継承する多重継承の一部の場合にのみ使用されます。この例はiostreamライブラリで、istreamとostreamはbasic_iosから継承し、iostreamはistreamとostreamから継承します(したがって、1つのiostreamには仮想継承なしで2つのbasic_iosがあります)。

この特定のシナリオでない限り、仮想継承を使用しないでください。

仮想継承についてはどうですか?この状況では、サブクラスのオブジェクトはそのスーパークラスのオブジェクトを直接保持しますか、それとも単にそのスーパークラスのオブジェクトを指すポインターを保持しますか?
それが実装定義です。あなたはこれについて知る必要はなく、またこれについていかなる仮定もするべきではありません。仮想継承には実行時のペナルティがあると言えば十分です。そのため、仮想継承が不要な場合は回避する必要があります。

于 2010-05-02T17:15:52.757 に答える
0

比較:

struct A {
    void *vptr; // offset 0 size 4 alignment 4
    char k[3]; // offset 4 size 3 alignment 1
    char unnamed_padding; // offset 7 size 1
    // total size 8 alignment 4
};

// MS:
struct base_B {
    void *vptr; // offset 0 size 4 alignment 4
    char j[3]; // offset 4 size 3 alignment 1
    char unnamed_padding; // offset 7 size 1
    A &a_subobject; // offset 8 size 4 alignment 4
    // total size 12 alignment 4

    base_B (&a_subobject) :a_subobject(a_subobject) {}
};

struct B {
    base_B b; // offset 0 size 12 alignment 4
    A a_subobject; // offset 12 size 8 alignment 4
    // total size 20 alignment 4

    B () : b(a_subobject) {}
};

struct base_C {
    void *vptr; // offset 0 size 4 alignment 4
    char i[3]; // offset 4 size 3 alignment 1
    char unnamed_padding; // offset 7 size 1
    A &a_subobject; // offset 8 size 4 alignment 4
    // total size 12 alignment 4

    base_C (&a_subobject) : a_subobject(a_subobject) {}
};

struct C {
    base_C c;
    A a_subobject; // offset 12 size 8 alignment 4
    // total size 20 alignment 4

    C () : c(a_subobject) {}
};

struct D {
    // no new vptr!
    // base_B is used as primary base: b_subobject.vptr is used as vptr
    base_B b_subobject; // offset 0 size 12 alignment 4
    base_C c_subobject; // offset 12 size 12 alignment 4
    char h[3];  // offset 24 size 3 alignment 1
    char unnamed_padding; // offset 27 size 1
    A a_subobject; // offset 28 size 8 alignment 4
    // total size 36 alignment 4

    D (): b_subobject(a_subobject), c_subobject(a_subobject) {}
};

// GCC:
struct base_B {
    void *vptr; // offset 0 size 4 alignment 4
    char j[3]; // offset 4 size 3 alignment 1
    char unnamed_padding; // offset 7 size 1
    // total size 8 alignment 4
};

struct B {
    base_B b; // offset 0 size 12 alignment 4
    A a_subobject; // offset 8 size 8 alignment 4
    // total size 16 alignment 4
};

struct base_C {
    void *vptr; // offset 0 size 4 alignment 4
    char i[3]; // offset 4 size 3 alignment 1
    char unnamed_padding; // offset 7 size 1
    // total size 8 alignment 4
};

struct C {
    base_C b; // offset 0 size 12 alignment 4
    A a_subobject; // offset 8 size 8 alignment 4
    // total size 16 alignment 4
};

struct D {
    // no new vptr!
    // base_B is used as primary base: b_subobject.vptr is used as vptr
    base_B b_subobject; // offset 0 size 8 alignment 4
    base_C c_subobject; // offset 8 size 8 alignment 4
    char h[3];  // offset 16 size 3 alignment 1
    char unnamed_padding; // offset 19 size 1
    A a_subobject; // offset 20 size 8 alignment 4
    // total size 24 alignment 4
};
于 2012-08-05T02:54:12.237 に答える