我决定了解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();
}
答案 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])()
。
现在最后定义一个继承自Derv
和Base
的类Base2
。这尤其意味着您可以同时指向Base*
类型的对象Base2*
和Derv
。现在,如果您只有一个__vptr
,则pBase->say()
和pBase2->lol()
会调用相同的函数,因为它们都会转换为(pXXX->__vptr[0])()
。
然而,实际发生的是两个 __vptr字段,一个用于Base
基类,一个用于_Base2
基类。 Base*
指向Base
子对象及其__vptr
,Base2*
指向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。