Why is this the same even when object pointers differ in multiple inheritance?

二次信任 提交于 2019-12-06 01:58:50

When multiple inheritance is used in a virtual function call, the call to the virtual function will often go to a 'thunk' that adjusts the this pointer. In your example, the casted1 pointer's vtbl entry doesn't need a thunk becuase the IDerived1 sub-object of the CClass happens to coincide with the start of the CClass object (which is why the casted1 pointer value is the same as the CClass object pointer).

However, the casted2 pointer to the IDerived2 sub-object doesn't coincide with the start of the CClass object, so the vtbl function pointer actually points to a thunk instead of directly to the CClass::Common() function. The thunk adjusts the this pointer to point to the actual CClass object then jumps to the CClass::Common() function. So it will always get a pointer to the start of the CClass object, regardless of which type of sub-object pointer it might have been called from.

There's a very good explanation of this in Stanley Lippman's "Inside the C++ Object Model" book, section 4.2 "Virtual Member Functions/Virtual Functions Under MI".

When casted to a different type, the offsets of fields as well as entries in the vtable have to be in a consistent place. Code that takes an ICommonBase* as a parameter doesn't know that your object is really an IDerived2. Yet it still should be able to dereference ->foo or call the virtual method bar(). If these aren't at predictable addresses that has no way of working.

For the single inheritance case, this is easy to get right. If Derived inherits from Base, you can just say that offset 0 of Derived is also offset 0 of Base, and the members unique to Derived can go after the last member of Base. For multiple inheritance obviously that can't work because the first byte of Base1 can't also be the first byte of Base2. Each one needs its own space.

So if you had such a class that inherits from two (call it Foo), the compiler can know that for the type Foo, the Base1 part starts at offset X, and the Base2 part starts at offset Y. When casting to either type, the compiler can just add the appropriate offset to this.

When an actual virtual method of Foo is called, where the implementation is provided by Foo, it still needs the "real" pointer to the object, so that it can access all of its members, not just the particular instance of the base Base1 or Base2. Hence this still needs to point to the "real" object.

Note that the implementation details of this may be different than described, this is just a high level description of why the problem exists.

If you see the object layout in memory for this it will be something like this:

v-pointer for IDerived1
v-pointer for IDerived2
....
....

It can be otherway also..but just to give an idea..

Your this will always point to the start of the object i.e. where the v-pointer for IDerived1 is stored. However, when you cast the pointer to IDetived2 the casted pointer will be pointing to the v-pointer for IDerived2 which will be offset by sizeof(pointer) from this pointer.

There's the same object model in G++ 4.3 as shown by you, (see answer of Naveen) say casted1 and casted2 have different values.

Furthermore, in G++ 4.3, even you use brutal casting:

ICommonBase* casted1 = (ICommonBase*)(IDerived1*)object; 
ICommonBase* casted2 = (ICommonBase*)(IDerived2*)object;

the result is the same.

Very clever the compiler

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!