Given your class hierarchy, an object of type B2 will have the following amount of memory.
+------------------------+ | pointer for B2 vtable | +------------------------+ | int_in_b2 | +------------------------+
An object of type D will have the following amount of memory.
+------------------------+ | pointer for B1 vtable | +------------------------+ | int_in_b1 | +------------------------+ | pointer for B2 vtable | +------------------------+ | int_in_b2 | +------------------------+ | int_in_d | +------------------------+
Using:
D* d = new D(); d->f2();
This call is the same as:
B2* b = new D(); b->f2();
f2() can be called using a type B2 pointer or a type D pointer. Given that the runtime should be able to work correctly with a pointer of type B2 , it should be able to correctly send a call to D::f2() using the corresponding function pointer in B2 vtable. However, when the call is sent to D:f2() , the original pointer of type B2 must somehow be correctly offset, so in D::f2() , this points to D , not B2 .
Here, your sample code has slightly changed to print useful pointer values ββand member data to help understand the changes in this value in various functions.
#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; }
Program Output:
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
When the actual object used to call test_f2() is D , the this value changes from 0x1f50040 in test_f2() to 0x1f50030 in D::f2() . This corresponds to sizeof B1 , B2 and D The offset of sub-object B2 object D is 16 (0x10) . The value of this in B::test_f2() , a B* , changes to 0x10 before the call is sent to D::f2() .
I am going to assume that the offset value from D to B2 stored in the B2 vtable. Otherwise, the distribution mechanism of common functions cannot correctly change the value of this before sending a call to the right virtual function.