Inside of Derived
's methods, the implicit this
pointer is always a Derived*
pointer (more generically, the this
pointer always matches the class type being called). That is why Derived::test()
and Derived::get()
can access the Derived::alex
member. That has nothing to do with Base
.
The memory layout of a Derived
object begins with the data members of Base
, followed by optional padding, followed by the data members of Derived
. That allows you to use a Derived
object wherever a Base
object is expected. When you pass a Derived*
pointer to a Base*
pointer, or a Derived&
reference to a Base&
reference, the compiler will adjust the pointer/reference accordingly at compile-time to point at the Base
portion of the Derived
object.
When you call b->test()
at runtime, where b
is a Base*
pointer, the compiler knows test()
is virtual
and will generate code that accesses the appropriate slot in b
's vtable and call the method being pointed at. But, the compiler doesn't know what derived object type b
is actually pointing at in runtime (that is the whole magic of polymorphism), so it can't automatically adjust the implicit this
pointer to the correct derived pointer type at compile-time.
In the case where b
is pointing at a Derived
object, b
's vtable is pointing at Derived
's vtable. The compiler knows the exact offset of the start of Derived
from the start of Base
. So, the slot for test()
in Derived
's vtable will point to a private stub generated by the compiler to adjust the implicit Base *this
pointer into a Derived *this
pointer before then jumping into the actual implementation code for Derived::test()
.
Behind the scenes, it is roughly (not exactly) implemented like the following pseudo-code:
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()...
The actual details are a bit more involved, but that should give you a basic idea of how polymorphism is able to dispatch virtual methods at runtime.