用于多继承的虚方法表

时间:2015-06-10 20:45:10

标签: c++ pointers this multiple-inheritance this-pointer

我正在读这篇文章“Virtual method table

上述文章中的示例:

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()的调用将B2指针作为this指针传递,因此我们必须执行以下操作:

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

为什么我们应该将B2指针作为this指针而不是原始D指针传递?我们实际上是在调用D :: f2()。根据我的理解,我们应该将D指针作为this传递给D :: f2()函数。

___更新____

如果将B2指针作为this传递给D :: f2(),如果我们想要访问D :: f2()中B1类的成员怎么办?我相信B2指针(this)显示如下:

          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

它已经具有该连续内存布局的起始地址的某个偏移量。例如,我们希望在D :: f2()中访问b1,我想在运行时,它会执行以下操作:*(this+4)this指向与b2相同的地址)这将指向b2中的B ????

2 个答案:

答案 0 :(得分:4)

我们无法将D指针传递给覆盖B2::f2()的虚函数,因为同一虚函数的所有覆盖都必须接受相同的内存布局。

由于B2::f2()函数希望B2的对象的内存布局作为其this指针传递给它,即

b2:
  +0: pointer to virtual method table of B2
  +4: value of int_in_b2

覆盖函数D::f2()也必须具有相同的布局。否则,这些功能将不再可以互换。

要了解互换性问题的原因,请考虑以下情况:

class B2 {
public:
  void test() { f2(); }
  virtual void f2() {}
  int int_in_b2;
};
...
B2 b2;
b2.test(); // Scenario 1
D d;
d.test(); // Scenario 2

B2::test()需要在两种情况下调用f2()。没有其他信息可以告诉它在进行这些调用 * 时如何调整this指针。这就是编译器传递固定指针的原因,因此test()f2的调用对D::f2()B2::f2()都有效。

* 其他实现可以很好地传递这些信息;但是,本文中讨论的多重继承实现并没有这样做。

答案 1 :(得分: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。鉴于运行时必须能够正确使用类型为B2的指针,它必须能够使用D::f2()中的相应函数指针正确地将调用分派给B2。虚函数表。但是,当调用被调度到D:f2()时,B2类型的原始指针必须以某种方式正确偏移,以便在D::f2()中,this指向D ,而不是B2

这是您的示例代码,稍作修改以打印有用的指针值和成员数据,以帮助理解各种函数中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时,this的值会从0x1f50040中的test_f2()更改为0x1f50030 D::f2()。这与sizeof B1B2D匹配。 B2对象的D子对象的偏移量为16 (0x10)this中的B::test_f2() B*的值在调用发送到0x10之前由D::f2()更改。

我猜测从DB2的偏移值存储在B2的vtable中。否则,通用函数调度机制无法在将调用分派给正确的虚函数之前正确地更改this的值。