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

标签 c++ virtual-functions vtable virtual-inheritance vptr

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

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”和“derived”对象中,为它们的 vtables 添加了一个 vptr。

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

最佳答案

编译器可以自由地将它放入一个现有的 vtable 中,就像正常的继承工作一样。虚拟继承保证您只有一次虚拟基类。

例如,clang 7 和 gcc 8.2 都将 child::xyzde() 放入 derivedc 的 vtable in child 中。参见“子虚表”(clang 7gcc 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 基类更改为 virtual,如下所示,您将获得三个单独的表:

class child: public virtual derivedc, public virtual derivedd

clang on godbolt :

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,如预期的那样 (clang on 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 - Part 3 - Virtual InheritanceVTTX-in-child 的构造 vtable 有一些简短的解释。

关于c++ - 了解虚拟继承类 vtables 和 vptr 创建,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52430120/

相关文章:

c++ - C++中可以不用纯虚函数实现抽象类吗?

c++ - C++ 中的通用模板

c++ - Volatile 关键字允许访问 UnitTest++ 中的 const 结构

c++ - 使用类对象提升线程 worker

c++ - 如何将 C++ 中的虚函数/基类转换为 C 编程?

c++ - 我可以在函数内执行 "variable statements"而没有定义吗?

.net - 在 .NET 中,当您传递一个类实例/接口(interface)作为参数时,您传递一个对象还是完整的 vtable

c++ - 虚函数和vtable是如何实现的?

c++ - 虚拟表真正存储在哪里,为什么我们不能修改它们?

c++ - Virtual destructor和Vtable之间有什么关系吗