7

この記事を読んでいる「仮想メソッド表

上記の記事の例:

class B1 {
public:
  void f0() {}
  virtual void f1() {}
  int int_in_b1;
};

class B2 {
public:
  virtual void f2() {}
  int int_in_b2;
};

class D : public B1, public B2 {
public:
  void d() {}
  void f2() {}  // override B2::f2()
  int int_in_d;
};

B2 *b2 = new B2();
D  *d  = new D();

この記事では、著者は、オブジェクトのメモリ レイアウトが次のdようになっていることを紹介しています。

          d:
D* d-->      +0: pointer to virtual method table of D (for B1)
             +4: value of int_in_b1
B2* b2-->    +8: pointer to virtual method table of D (for B2)
             +12: value of int_in_b2
             +16: value of int_in_d

Total size: 20 Bytes.

virtual method table of D (for B1):
  +0: B1::f1()  // B1::f1() is not overridden

virtual method table of D (for B2):
  +0: D::f2()   // B2::f2() is overridden by D::f2()

についての質問ですd->f2()。への呼び出しはポインターをポインターとしてd->f2()渡すため、次のようにする必要があります。B2this

(*(*(d[+8]/*pointer to virtual method table of D (for B2)*/)[0]))(d+8) /* Call d->f2() */

元のポインターではなく、B2ポインターをポインターとして渡す必要があるのはなぜですか??? 実際に D::f2() を呼び出しています。私の理解に基づいて、D::f2() 関数としてポインターを渡す必要があります。thisDDthis

___アップデート____

B2D::f2() にポインタを渡す場合、thisD::f2() 内のクラスのメンバにアクセスしたい場合はB1?? ポインタB2(これ)は次のように表示されると思います:

          d:
D* d-->      +0: pointer to virtual method table of D (for B1)
             +4: value of int_in_b1
B2* b2-->    +8: pointer to virtual method table of D (for B2)
             +12: value of int_in_b2
             +16: value of int_in_d

この連続したメモリ レイアウトの開始アドレスには、既に特定のオフセットがあります。たとえば、b1D::f2() 内にアクセスしたい場合、実行時に次のようになると思います: *(this+4)( thisb2 と同じアドレスを指す)b2B????を指します。

4

2 に答える 2

1

クラス階層を考えると、型のオブジェクトにB2は次のメモリ フットプリントがあります。

+------------------------+
| pointer for B2 vtable  |
+------------------------+
| int_in_b2              |
+------------------------+

型のオブジェクトにDは、次のメモリ フットプリントがあります。

+------------------------+
| pointer for B1 vtable  |
+------------------------+
| int_in_b1              |
+------------------------+
| pointer for B2 vtable  |
+------------------------+
| int_in_b2              |
+------------------------+
| int_in_d               |
+------------------------+

使用する場合:

D* d  = new D();
d->f2();

その呼び出しは次と同じです。

B2* b  = new D();
b->f2();

f2()B2型のポインターまたは型のポインターを使用して呼び出すことができますD。ランタイムがタイプ のポインターを正しく処理できなければならない場合、の vtable内の適切な関数ポインターを使用して、B2への呼び出しを正しくディスパッチできなければなりません。ただし、呼び出しが元の型のポインターにディスパッチされると、 in がではなくを指すように、何らかの形で適切にオフセットする必要があります。D::f2()B2D:f2()B2D::f2()thisDB2

thisこれがサンプルコードです。さまざまな関数の値の変更を理解するのに役立つように、有用なポインター値とメンバーデータを出力するように少し変更されています。

#include <iostream>

struct B1 
{
   void f0() {}
   virtual void f1() {}
   int int_in_b1;
};

struct B2 
{
   B2() : int_in_b2(20) {}
   void test_f2()
   {
      std::cout << "In B::test_f2(), B*: " << (void*)this << std::endl;
      this->f2();
   }

   virtual void f2()
   {
      std::cout
         << "In B::f2(), B*: " << (void*)this
         << ", int_in_b2: " << int_in_b2 << std::endl;
   }

   int int_in_b2;
};

struct D : B1, B2 
{
   D() : int_in_d(30) {}
   void d() {}
   void f2()
   {
      // ======================================================
      // If "this" is not adjusted properly to point to the D
      // object, accessing int_in_d will lead to undefined 
      // behavior.
      // ======================================================

      std::cout
         << "In D::f2(), D*: " << (void*)this
         << ", int_in_d: " << int_in_d << std::endl;
   }
   int int_in_d;
};

int main()
{
   std::cout << "sizeof(void*) : " << sizeof(void*) << std::endl;
   std::cout << "sizeof(int)   : " << sizeof(int) << std::endl;
   std::cout << "sizeof(B1)    : " << sizeof(B1) << std::endl;
   std::cout << "sizeof(B2)    : " << sizeof(B2) << std::endl;
   std::cout << "sizeof(D)     : " << sizeof(D) << std::endl << std::endl;

   B2 *b2 = new B2();
   D  *d  = new D();
   b2->test_f2();
   d->test_f2();
   return 0;
}

プログラムの出力:

sizeof(void*) : 8
sizeof(int)   : 4
sizeof(B1)    : 16
sizeof(B2)    : 16
sizeof(D)     : 32

In B::test_f2(), B*: 0x1f50010
In B::f2(), B*: 0x1f50010, int_in_b2: 20
In B::test_f2(), B*: 0x1f50040
In D::f2(), D*: 0x1f50030, int_in_d: 30

呼び出しに使用される実際のオブジェクトtest_f2()Dである場合、 の値はinからinにthis変わります。これは sizeof 、、および と一致します。オブジェクトのサブオブジェクトのオフセットは です。inの値aは、呼び出しが にディスパッチされる前にによって変更されます。0x1f50040test_f2()0x1f50030D::f2()B1B2DB2D16 (0x10)thisB::test_f2()B*0x10D::f2()

からのオフセットの値がの vtableに格納されDていると推測します。そうしないと、ジェネリック関数ディスパッチ メカニズムが適切な仮想関数への呼び出しをディスパッチする前に、 の値を適切に変更する方法がありません。B2B2this

于 2015-06-11T03:23:54.240 に答える