Внутри методов Derived
неявный указатель this
всегда является указателем Derived*
(в более общем смысле указатель this
всегда соответствует вызываемому типу класса). Вот почему Derived::test()
и Derived::get()
могут получить доступ к члену Derived::alex
. Это не имеет ничего общего с Base
.
Структура памяти объекта Derived
начинается с элементов данных Base
, за которыми следует необязательное заполнение, после которого следуют элементы данных Derived
. Это позволяет вам использовать объект Derived
везде, где ожидается объект Base
. Когда вы передаете указатель Derived*
на указатель Base*
или ссылку Derived&
на ссылку Base&
, компилятор будет корректировать указатель / ссылку соответствующим образом во время компиляции, чтобы указывать на часть Base
Derived
объект.
Когда вы вызываете b->test()
во время выполнения, где b
является указателем Base*
, компилятор знает, что test()
is virtual
и сгенерирует код, который обращается к соответствующему слоту в vtable b
и вызовите метод, на который указывает Но компилятор не знает, на какой тип производного объекта b
на самом деле указывает время выполнения (в этом вся магия полиморфизма), поэтому он не может автоматически настроить неявный указатель this
на правильный тип производного указателя. во время компиляции.
В случае, когда b
указывает на объект Derived
, vtable b
указывает на vtable Derived
. Компилятор знает точное смещение начала Derived
от начала Base
. Таким образом, слот для test()
в vtable Derived
будет указывать на частную заглушку, сгенерированную компилятором, чтобы настроить неявный указатель Base *this
на указатель Derived *this
, а затем перейти к фактическому коду реализации для * 1046. *.
За кулисами это примерно (не совсем) реализовано как следующий псевдокод:
void Derived_test_stub(Base *this)
{
Derived *adjusted_this = reinterpret_cast<Derived*>(reinterpret_cast<uintptr_t>(this) + offset_from_Base_to_Derived);
Derived::test(adjusted_this);
}
int Derived_get_stub(Base *this)
{
Derived *adjusted_this = reinterpret_cast<Derived*>(reinterpret_cast<uintptr_t>(this) + offset_from_Base_to_Derived);
return Derived::get(adjusted_this);
}
struct vtable_Base
{
void* funcs[2] = {&Base::test, &Base::get};
};
struct vtable_Derived
{
void* funcs[2] = {&Derived_test_stub, &Derived_get_stub};
};
Base::Base()
{
this->vtable = &vtable_Base;
bob = 0;
}
Derived::Derived() : Base()
{
Base::vtable = &vtable_Derived;
this->vtable = &vtable_Derived;
alex = 0;
}
...
Base *b = new Derived;
//b->test(); // calls Derived::test()...
typedef void (*test_type)(Base*);
static_cast<test_type>(b->vtable[0])(b); // calls Derived_test_stub()...
//int i = b->get(); // calls Derived::get()...
typedef int (*get_type)(Base*);
int i = static_cast<get_type>(b->vtable[1])(b); // calls Derived_get_stub()...
Фактические детали немного сложнее, но это должно дать вам базовое представление о том, как полиморфизм способен отправлять виртуальные методы во время выполнения.