I have been reading on vtables and pointers, but there is a few questions I still have. For example:
#include <iostream>
using namespace std;
class A
{
public:
virtual void PrintA()=0; //1 vtable and 1 vpointer
};
class B
{
public:
virtual void PrintB()=0; //1 vtable and 1 vpointer
};
class Parent: public A, public B
{
public:
void PrintA();
void PrintB(); // 3 vtables and 3 vpointers, right?
virtual void PrintChild()=0;
};
void Parent::PrintA()
{
cout<<"A";
}
void Parent::PrintB()
{
cout<<"B";
}
class Child: public Parent
{
public:
void PrintChild(); //3 vtables and 3 vpointers
};
void Child::PrintChild()
{
cout<<"Child";
}
int main()
{
Parent* p1= new Child();
p1->PrintChild();
delete p1;
return 0;
}
Question 1: Will Parent and Child will have 3 vtables and 3 vpointers?
Question 2: How will p1 know which vpointer to use? I heard it is up to the compiler but I just want someone clarify.
Yes, the definitive answers are compiler dependent. There isn't even a guarantee that virtual dispatch will be implemented with vtables.
Often a compiler will follow a particular platform's ABI. On many systems GCC implements a particular ABI that was invented for IA-64, but then was ported to other systems. This is readily available online, there are links from the GCC web site.
One way to see more about vtables in practice, on Linux at least, is to use
gdb
, compile a small example program with-g
, and useinfo vtbl
to explore the vtable. However, this is a bit tricky at present due to a GCC bug involving debug info for virtual destructors; so just be sure to always have methods other than destructors.I compiled your program and stopped in
gdb
afterp1
was initialized. Then:Here you an see that
Parent
andChild
actually just have two vtables, not three. This is because in this ABI, single inheritance is implemented by extending the parent class' vtable; and in this case, the extension ofA
is treated this same way.As to how
p1
knows which vtable to use: it depends on the actual type that is used to make the call.In the code,
p1->PrintChild()
is called, andp1
is aParent*
. Here, the call will be made via the first vtable you see above -- because nothing else makes sense, asPrintChild
isn't declared inB
. In this ABI, the vtable is stored in the first slot of the object:Now, if you changed your code to cast
p1
to aB*
, then two things would happen. First, the raw bits of the pointer would change, because the new pointer would point to a subobject of the full object. Second, this subobject would have its vtable slot pointing to the second vtable mentioned above. In this scenario, sometimes a special offset is applied to the subobject to find the full object again. There are also some special tweaks that apply whenvirtual
inheritance is used (this complicates object layout a bit, because the superclass in question only appears once in the layout).You can see these changes like this:
This is pretty much all specific to the ABI commonly used on Linux. Other systems may make different choices.