Reputation: 39079
I have a class implementing two abstract classes, like the following. No virtual inheritance. No data member.
class IFace1 {
public:
virtual void fcn(int abc) = 0;
};
class IFace2 {
public:
virtual void fcn1(int abc) = 0;
};
class RealClass: public IFace1, public IFace2 {
public:
void fcn(int a) {
}
void fcn1(int a) {
}
};
And I find the vtable and object memory layout for RealClass is like the following.
Vtable for RealClass
RealClass::_ZTV9RealClass: 7u entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI9RealClass)
16 (int (*)(...))RealClass::fcn
24 (int (*)(...))RealClass::fcn1
32 (int (*)(...))-8
40 (int (*)(...))(& _ZTI9RealClass)
48 (int (*)(...))RealClass::_ZThn8_N9RealClass4fcn1Ei
Class RealClass
size=16 align=8
base size=16 base align=8
RealClass (0x2af836d010e0) 0
vptr=((& RealClass::_ZTV9RealClass) + 16u)
IFace1 (0x2af836cfa5a0) 0 nearly-empty
primary-for RealClass (0x2af836d010e0)
IFace2 (0x2af836cfa600) 8 nearly-empty
vptr=((& RealClass::_ZTV9RealClass) + 48u)
I am confused about this. What is RealClass::_ZThn8_N9RealClass4fcn1Ei? Why the vptr of IFace2 points to that? What happens when I call fcn1 from IFace2 *? How does the program finds RealClass::fcn1 in the Vtable of RealClass? I guess it somehow need to use the IFace2 vptr, but not clear exactly how.
Upvotes: 3
Views: 1353
Reputation: 13818
Warning: Most of the stuff below is of course implementation and platform dependent and simplified. I'll follow the way I see it is implemented in your examples -- probably GCC, 64-bits.
First, what is the contract for instances of virtual classes? E.g. if you have a variable IFace1* obj
:
sizeof(void*)
).void fcn(int)
at vtbl+0.typeinfo
of the class at vtbl-8 (used by dynamic_cast
etc.) and "offset to base" at vtbl-16.Any function which sees a variable of type IFace1*
can depend on this being true. Similarly for IFace2*
.
void fcn(int)
, they look at obj+0 to get the vtable, then at vtbl+0 and call the address found there. this
is set to obj.typeinfo
pointer of the vtable referenced by the base object.Now, how can you compiler satisfy these requirements for a class with multiple inheritance?
1) First it needs to generate the structure for itself. The virtual table pointer must be at obj+0, so there it is. How will the table look like? Well, the offset to base is 0, obviously, the typeinfo
data and pointer to it is generated easily, then the first virtual function and the second, nothing special. Anyone who knows the definition of RealClass
can do the same calculations, so they know where to find the functions in the vtable etc.
2) Then it goes to make it possible to let RealClass
be passed around as IFace1
. So it needs to have a pointer to virtual table in the IFace1
format somewhere in the object, then the virtual table must have that one record for void fcn(int)
.
The compiler is clever and sees that it can reuse the first virtual table it has generated, because it complies with these requirements. If there were any member fields, they would be stored after the first pointer to the virtual table, so even them could be accessed simply as if the derived class was the base one. So far so good.
3) Finally, what to do with the object so others will be able to use it as IFace2
? The one vtable already created cannot be used anymore, because IFace2
needs its void fcn1(int)
to be at vtbl+0.
So another virtual table is created, the one you see immediately following the first one in your dump, and a pointer to it is stored in RealClass
at the next available place. This second table needs to have offset to base set to -8, because the real object starts at offset -8. And it contains just the pointer to that IFace2
virtual function, void fcn1(int)
.
The virtual pointer in the object (at offset obj+8) would be then followed by any member data fields of IFace2
, so that any inherited or inline functions could again work when given the pointer to this interface.
OK, now how can someone call the fcn1()
from IFace2
? What is that non-virtual thunk to RealClass::fcn1(int)
?
If you pass your RealClass*
pointer to a stranger function which takes IFace2*
, the compiler will emit code to increase your pointer by 8 (or however large sizeof(void*) + sizeof(IFace1)
is), so that the function gets the pointer which starts with the virtual table pointer of IFace2
, then its member fields -- just as agreed in the contract I outlined earlier.
When that function wants to call void IFace2::fcn1(int)
, it looks into the virtual table, goes to the record of this particular function (the first and only one) and calls it, with this
set to the address being passed as pointer to IFace2
.
And here arises a problem: If someone invokes this method implemented in RealClass
on a RealClass
pointer, this
points to the base of RealClass
. The same with IFace1
. But if it is invoked by someone having a pointer to the IFace2
interface, this
points 8 (or however many) bytes into the object instead!
So the compiler would need to generate the function multiple times to accomodate this, otherwise it could not access member fields and other methods correctly, as it differs depending on who is calling the method.
Instead of having the code really twice, the compiler optimizes this by creating that hidden implicit small thunk function instead, which just
this
pointer by the proper amount,Upvotes: 4