0

次のクラス階層があるとします。

class A {
int x;
public:
A(int X) : x(X) {}
void setX(int x) { this->x = x; }
};



class B : public virtual  A {
int y;
public:
B(int X, int Y) : A(X), y(Y) {}
};

class C : public virtual A {
int z;
public:
C(int X, int Z) : A(X), z(Z) {}
};

class D : public C, public B {
public:
D(int x, int y, int z) : A(x), C(x,z), B(x,y) {}
};

そして次のメイン:

int main (void)
{
D x(2,3,4);
A* temp1 = &x;
B* temp2 = &x;
C* temp3 = &x;
}

temp1、temp2、および temp3 はすべて異なるアドレスを指しているようです.. B と C は同じ A オブジェクトを共有すべきではありませんか? 結局のところ、すべての C および B オブジェクトも A であるため、ポインターは最初に A オブジェクトを「見る」必要があります..いいえ? さらに、C ポインタには、D オブジェクトである X のアドレスが含まれています。なぜ?

ここにメモリマップがあります:

&x      0x0036f828 {...}    D *
temp1   0x0036f838 {x=5 }   A *
temp2   0x0036f830 {y=3 }   B *
temp3   0x0036f828 {z=4 }   C *
4

3 に答える 3

0

これは、オブジェクトがメモリ内で表現される方法です。

したがって、オブジェクトは次のようになります。

+ 0x0036f828
- D
- int z (C)
- int y (B) 
- int x (A)

C++ キャストは、オブジェクトの先頭のオフセットを与えるだけです。したがって、オフセットは、クラスA、B、C
の整数のサイズ(それが含まれているため) と、クラスB、Cの仮想テーブルであることがわかります。Dにはメンバーがないため、オフセットは 0 です 。

C++ コンパイラは、メンバーと基本クラスを実際に記述する順序でメモリ レイアウトを設定することに注意してください。

したがって、D の基本クラスの順序を変更すると、異なる結果が得られます。

class D : public B, public C

これで、 Bが D の後のレイアウトの最初のクラスになります。

于 2013-04-13T04:55:27.483 に答える
0

単純な構造体を使用して書き出すと、次のようになります。

struct A {
  int x;
};

struct B {
  A *ap;
  int y;
};

struct C {
  A *ap;
  int z;
};

struct D {
  C c;
  B b;
  A a;
};

int main (void)
{
  D x;
  A* temp1 = &x.a;
  B* temp2 = &x.b;
  C* temp3 = &x.c;
}

B仮想継承を使用するためC、実際のオブジェクトではなく、ベースへのポインターのみが含まれます。cは の先頭にあるDため、同じアドレスになることがわかります。

于 2013-04-13T05:10:02.353 に答える
0

B と C が同じ A オブジェクトを共有する必要があるという点では正しいです。そして、それはまさにここで起こることです。表示されているアドレスは、実際には各クラスに固有の仮想テーブルのアドレスです。仮想継承の場合、各クラスの仮想テーブルには、仮想基本クラス (この場合はオブジェクト A) へのポインターが含まれます。

したがって、クラス B と C の両方の仮想テーブルには、それぞれがオブジェクト A の同じアドレスを指すポインターがあります。

于 2013-04-13T04:47:02.043 に答える