таблица виртуальных методов для множественного наследования
я читаю эту статью"таблица виртуальных методов"
пример в приведенной выше статье:
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
суб-объект