c++classvirtualvtablevptr

Virtual table pointers


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();
}

enter image description here


Solution

  • 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.