The table of virtual methods for multiple inheritance

I am reading this article " Virtual Method Table "

An example in the above article:

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

In the article, the author introduces that the memory layout of the object d looks like this:

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

Question about d->f2() . Calling d->f2() passes the B2 pointer as the this pointer, so we should do something like:

 (*(*(d[+8]/*pointer to virtual method table of D (for B2)*/)[0]))(d+8) /* Call d->f2() */ 

Why do we need to pass the B2 pointer as the this pointer, and not the original d pointer ??? In fact, we call D :: f2 (). Based on my understanding, we should pass the pointer d like this to the function D :: f2 ().

___ update ____

If we pass the B2 pointer as this to D :: f2 (), what if we want to access the members of class B1 in D :: f2 () ?? I believe the B2 (this) pointer is shown as follows:

  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 

It already has a certain offset of the starting address of this contiguous memory. For example, we want to access B1 inside D :: f2 (), I assume that at runtime it will do something like: *(this+4) ( this points to the same address as b2), which points B2 to B ????

+7
c ++ pointers this multiple-inheritance this-pointer
source share
2 answers

We cannot pass pointer D to a virtual function that overrides B2::f2() , because all overrides of the same virtual function must accept the same memory layout.

Since the function B2::f2() expects a B2 layout of the memory of the object passed to it as its this pointer, i.e.

 b2: +0: pointer to virtual method table of B2 +4: value of int_in_b2 

the override function D::f2() should expect the same layout. Otherwise, the functions will no longer be interchangeable.

To understand why interchangeability issues consider this scenario:

 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() needs to make a call to f2() in both scripts. He has no additional information to say how to adjust the this pointer when making these calls * . This is why the compiler passes the copied pointer, so the test() f2 call will work with both D::f2() and B2::f2() .

* Other implementations can convey this information very well; however, the multiple implementation of inheritance discussed in the article does not.

+4
source share

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.

+1
source share

All Articles