2

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

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

在此处输入图像描述

4

2 回答 2

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])().

现在终于定义了一个Derv继承自Base和的类Base2。这尤其意味着您可以同时拥有 aBase*和 aBase2*指向类型的对象Derv。现在,如果您只有一个__vptrpBase->say()并且pBase2->lol()会调用相同的函数,因为它们都转换为(pXXX->__vptr[0])().

然而实际发生的是有两个__vptr 字段,一个用于Base基类,一个用于_Base2基类。用它的Base*指向子对象,用它自己的指向子对象。现在虚拟表可能看起来像这样:Base__vptrBase2*Base2__vptrDerv

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

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

于 2014-05-19T20:27:56.303 回答
1

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

于 2014-05-19T20:09:54.957 回答