虚拟表指针

时间:2014-05-19 19:56:20

标签: c++ class virtual vtable vptr

我决定了解vtable是如何构建的。所以我打开调试器并发现了一些奇怪的东西。节点 ptr 包含一些vptr。我一直认为每个对象只有一个vptr。谁能向我解释这里发生了什么? (我的意思是当Base类指针指向Derived类的对象时)

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

http://s018.radikal.ru/i504/1405/1e/38832e978dd5.jpg

2 个答案:

答案 0 :(得分:4)

需要两个指针,因为你有两个带虚函数的基类。

让我们一步一步地完成它:

首先定义具有虚函数的Base。因此编译器将创建一个大致如下所示的虚拟表(括号中给出的索引;请注意,这是一个示例,确切的表布局将取决于编译器):

[0] address of Base::say()
[1] address of Base::no()

Base布局中,将有一个字段__vptr(或者它被命名,如果它被命名,则指向该表)。当给出pBase类型的指针Base*并要求调用say时,编译器实际上会调用(p->__vptr[0])()

接下来,您定义第二个独立的类Base2,其虚拟表将如下所示:

[0] address of Base2::lol()

通过lol指针调用Base2现在会转换为(pBase2->__vptr[0])()

现在最后定义一个继承自DervBase的类Base2。这尤其意味着您可以同时指向Base*类型的对象Base2*Derv。现在,如果您只有一个__vptr,则pBase->say()pBase2->lol()会调用相同的函数,因为它们都会转换为(pXXX->__vptr[0])()

然而,实际发生的是两个 __vptr字段,一个用于Base基类,一个用于_Base2基类。 Base*指向Base子对象及其__vptrBase2*指向Base2子对象及其__vptr。现在Derv虚拟表可能看起来像像这样:

[0] address of Derv::say()
[1] address of Base::no()
[2] address of Base2::lol()

__vptr子对象的Base指向该表的开头,而__vptr子对象的Base2指向元素[2]。现在,调用pBase->say()将转换为(pBase->__vptr[0])(),并且由于__vptr子对象的Base指向Derv虚拟表的开头,最终会按预期调用Derv::say()。另一方面,如果您致电pBase2->lol(),它将被翻译为(pBase2->__vptr[0])(),但由于pBase2指向Base2子对象od Derv,因此取消引用指向__vptr虚拟表的元素[2]的相应Derv,其中存储Base2::lol的地址。所以现在按预期调用Base2::lol()

答案 1 :(得分:1)

考虑将指针派生到指向基类的指针时会发生什么,它必须引用与基类型具有相同布局的内存块。当你有多个继承时,你将在每个具有虚函数的基础中得到一个vptr。