了解虚拟继承类 vtables 和 vptr 创建

Understanding virtual inheritance class vtables and vptr creation

本文关键字:vptr 创建 vtables 虚拟 继承 了解      更新时间:2023-10-16

下面的代码是多重继承的,其中每个类都有一个成员变量,一个普通函数和一个虚函数。

class basec
{
int x;
public:
basec()
{
x = 0;
}
void print()
{}
virtual void xyz()
{}
};
class derivedc: public virtual basec
{
int dc;
public:
derivedc()
{
dc = 0;
}
virtual void xyzdc()
{}
};
class  derivedd: public virtual basec
{
int dd;
public:
derivedd()
{
dd = 0;
}
virtual void xyzdd()
{}
};
class child: public derivedc, public derivedd
{
char de;
public:
child()
{
de = '4';
}
virtual void xyzde()
{}
};
main(int argc, char **argv)
{
basec bobj, bobjc;    
derivedc dcobj;
derivedd ddobj;
child deobj;
std::cout << "C " << sizeof(basec) << endl;
std::cout << "D " << sizeof(derivedc) << endl;
std::cout << "E " << sizeof(derivedd) << endl;
std::cout << "F " << sizeof(child) << endl;
return(0);
}

程序输出如下:

main()
C 8
D 16
E 16
F 28

看到gdb 中的每个对象,我在下面看到:

(gdb) p/x bobj
$1 = {_vptr.basec = 0x8048c80, x = 0x0}
(gdb) p/x dcobj
$3 = {<basec> = {_vptr.basec = 0x8048c5c, x = 0x0}, _vptr.derivedc = 0x8048c4c, dc = 0x0}
(gdb) p/x ddobj
$4 = {<basec> = {_vptr.basec = 0x8048c1c, x = 0x0}, _vptr.derivedd = 0x8048c0c, dd = 0x0}
(gdb) p/x deobj
$5 = {<derivedc> = {<basec> = {_vptr.basec = 0x8048b90, x = 0x0}, _vptr.derivedc = 0x8048b6c, dc = 0x0}, <derivedd> = {_vptr.derivedd = 0x8048b80, dd = 0x0}, de = 0x34}

我们看到,在基类"basec"和每个虚拟派生类"derivedc"和"derivedd"对象中,为其vtable添加了vptr。

问题是为什么子类尽管有一个虚函数,但没有自己的vtable,为什么它的对象中没有自己的vptr? 子类的虚拟函数会出现在哪个类的 vtable 中?

编译器可以自由地将其放在现有 vtables 上,就像正常的继承工作一样。虚拟继承保证您只拥有一次虚拟基类。

例如,clang 7 和 gcc 8.2 都把child::xyzde()放在childderivedc的 vtable 中。参见"vtable for child"(glang 7 和 gcc 8.2 on godbolt(。

class derivedc : public virtual basec
class derivedd : public virtual basec
class child: public derivedc, public derivedd
vtable for child:
.quad 32
.quad 0
.quad typeinfo for child
.quad derivedc::xyzdc()
.quad child::xyzde()       <- child::xyzde() together with derivedc's methods
.quad 16
.quad -16
.quad typeinfo for child
.quad derivedd::xyzdd()
.quad 0
.quad -32
.quad typeinfo for child
.quad basec::xyz()         <- basec is only once in child

如果将child基类更改为虚拟类,如下所示,您将获得三个单独的表:

class child: public virtual derivedc, public virtual derivedd

叮当在神霹雳上:

vtable for child:
.quad 48
.quad 32
.quad 16
.quad 0
.quad typeinfo for child
.quad child::xyzde()      <- New vtable for child
.quad 0
.quad 16
.quad -16
.quad typeinfo for child
.quad derivedc::xyzdc()
.quad 0
.quad -32
.quad typeinfo for child
.quad basec::xyz()        <- basec is only once in child
.quad 0
.quad -16
.quad -48
.quad typeinfo for child
.quad derivedd::xyzdd()

如果你删除所有虚拟继承,你会得到两次basecchild如预期的那样(在 godbolt 上叮当(。

class derivedc : public basec
class derivedd : public basec
class child: public derivedc, public derivedd
vtable for child:
.quad 0
.quad typeinfo for child
.quad basec::xyz()        <- basec from derivedc
.quad derivedc::xyzdc()
.quad child::xyzde()      <- child::xyzde() together with derivedc's methods
.quad -16
.quad typeinfo for child
.quad basec::xyz()        <- basec from derivedd
.quad derivedd::xyzdd()

C++ vtables - 第 3 部分 - 虚拟继承对VTTconstruction vtable for X-in-child有一些简短的解释。