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.