I decided to find out how vtable is built realy.So I opened debugger and found out some weird thing. The node ptr contains a few vptr. I always thought that there was only one vptr per object. Could anybody explain to me what's going on here ? (I mean when Base class pointer points to an object of Derived class)
#include <iostream>
using namespace std;
class Base
{
int base;
public:
virtual void say()
{
cout << "Hello" << endl;
}
virtual void no()
{
cout << "No" << endl;
}
};
class Base2
{
public:
virtual void lol()
{
cout << "lol" << endl;
}
};
class Derv:public Base,public Base2
{
public:
void say()
{
cout << "yep" << endl;
}
};
int main()
{
Base* ptr = new Derv();
ptr->say();
ptr = new Base();
ptr->say();
}
Two pointers are needed because you have two base classes with virtual functions.
Let's go through it step by step:
You first define Base which has virtual functions. Therefore the compiler will create a virtual table which roughly looks as follows (the indices given in brackets; note that this is an example, the exact table layout will depend on the compiler):
[0] address of Base::say()
[1] address of Base::no()
In the Base layout there will be a field __vptr (or however it is named, if it is named at all) pointing to that table. When given a pointer pBase of type Base* and asked to call say, the compiler will actually call (p->__vptr[0])().
Next you define a second, independent class Base2, whose virtual table will look like this:
[0] address of Base2::lol()
A call to lol through a Base2 pointer will now translated to something like (pBase2->__vptr[0])().
Now finally you define a class Derv which inherits from both Base and Base2. This especially means you can have both a Base* and a Base2* pointing to an object of type Derv. Now if you only had one __vptr, pBase->say() and pBase2->lol() would call the same function, because they both translate to (pXXX->__vptr[0])().
However what actually happens is that there are two __vptr fields, one for the Base base class, and one for the _Base2 base class. The Base* points to the Base subobject with its __vptr, the Base2* points to the Base2 subobject with its own __vptr. Now the Derv virtual table may look e.g. like this:
[0] address of Derv::say()
[1] address of Base::no()
[2] address of Base2::lol()
The __vptr of the Base subobject points at the beginning of that table, while the __vptr of the Base2 subobject points to element [2]. Now calling pBase->say() will translate to (pBase->__vptr[0])(), and since the __vptr of the Base subobject points to the beginning of Derv's virtual table, it will end up calling Derv::say() as intended. On the other hand, if you call pBase2->lol() it will be translated to (pBase2->__vptr[0])(), but since pBase2 points to the Base2 subobject od Derv, it will thus dereference the corresponding __vptr which points to element [2] of Derv's virtual table, where the address of Base2::lol is stored. So now Base2::lol() is called as intended.