理解虚继承类的虚函数表和虚指针创建

4
下面的代码是多重继承,每个类都有一个成员变量、一个普通函数和一个虚函数。
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”的对象中都添加了一个vptr用于它们的vtable。
问题是,为什么子类即使有虚函数也没有自己的vtable,并且在其对象中也没有自己的vptr? 在哪个类的vtable中会出现子类的虚函数?

感谢Tim的回复。我会尝试您的建议并进行检查。 - RKum
1
例如,clang 7将child :: xyzde()放在derivedc中的vtable中,在子类中查看“ child的vtable”-https://gcc.godbolt.org/z/d2VWza。如果您将`child`更改为`class child:public virtual derivedc,public virtual derivedd,则会得到三个单独的表格。 https://gcc.godbolt.org/z/wCbmWb。如果您删除所有virtual继承,则会按预期复制basec` -https://gcc.godbolt.org/z/8wxPGA。 - Mihayl
1
`vtable for child:... .quad derivedc::xyzdc() .quad child::xyzde() .... .quad typeinfo for child .quad derivedd::xyzdd() ... .quad typeinfo for child .quad basec::xyz()` - Mihayl
问题在于为什么子类虽然有虚函数,但没有自己的vtable?-但它确实有!事实上,它有三个,位于0x8048b900x8048b6c0x8048b80。它只是没有自己的vptr,因为那完全是多余的。 - Sebastian Redl
1
也许你需要尝试一些非常简单的继承案例,看看有多少个vptr被使用以及vtable在理解“菱形继承”之前包含了什么。 - curiousguy
显示剩余3条评论
1个回答

2
编译器可以将其放在现有虚函数表之一中,就像普通继承一样。虚继承保证您只拥有一个虚基类。
例如,clang 7和gcc 8.2都将child :: xyzde()放在childderivedc的虚函数表中。请参见“child的虚函数表” (clang 7godbolt上的gcc 8.2)。
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 的基类更改为 virtual,如下所示,则会得到三个单独的表:
class child: public virtual derivedc, public virtual derivedd

在 Godbolt 上使用 Clang

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

如果您移除所有虚继承,您会在child中如预期地两次得到basec在godbolt上使用clang)。

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 - Part 3 - Virtual Inheritance介绍了VTTX-in-child构造vtable的简短说明。


C++ vtables - 第三部分 - 虚继承提供了一个很好的解释。 - RKum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接