таблица виртуальных методов для множественного наследования

я читаю эту статью"таблица виртуальных методов"

пример в приведенной выше статье:

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 ().

_ _ _ update_____

при прохождении B2 указатель this в D:: f2(), что делать, если мы хотим получить доступ к членам B1 класс В D:: f2()?? Я верю 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

он уже имеет определенное смещение начального адреса это смежная компоновка памяти. Например, мы хотим получить доступ b1 внутри D:: f2 (), я думаю, во время выполнения он будет делать что-то вроде: *(this+4) (this указывает на тот же адрес, что и b2), который указывает b2 на B????

2 ответов


мы не можем пройти D указатель на виртуальную функцию переопределения 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 указатель должен быть скорректирован при выполнении этих вызовов*. Вот почему компилятор передает фиксированный указатель, so test()зов f2 будет работать как с D::f2() и B2::f2().

* другой реализации могут очень хорошо передать эту информацию; однако реализация множественного наследования, обсуждаемая в статье, этого не делает.


учитывая вашу иерархию классов, объект типа 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 ' s vtable. Однако, когда вызов отправляется в D:f2() исходный указатель типа B2 должен каким-то образом быть смещен должным образом, чтобы в D::f2(), this точки D, а не B2.

вот ваш пример кода, немного измененный для печати полезных значений указателей и данных членов, чтобы помочь понять изменения значения this in различные функции.

#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() is D, стоимостью this изменения 0x1f50040 на test_f2() to 0x1f50030 на D::f2(). Это соответствует sizeof B1, B2 и D. Смещение B2 суб-объект