c++中虚拟继承的对象布局

object layout of virtual inheritance in c++

本文关键字:对象 布局 继承 虚拟 c++      更新时间:2023-10-16
  • c++中虚拟继承的对象布局
  • 如何解释下面的结果

环境与2008年相比

  #include <iostream>
  using namespace std;
    class A {
    public:
    virtual ~A();
    virtual void foo();
    };
    class B : public virtual A {
    public:
    virtual ~B();
    virtual void foo();
    };
    class C : public virtual A {
    public:
    virtual ~C();
    virtual void foo();
    virtual void foobar();
    };
    class D : public B, public C {
    public:
    virtual ~D();
    virtual void foo();
    virtual void foobar();
    };
    int main()
    {
    cout<<"size of A "<<sizeof(A)<<endl;
    cout<<"size of B "<<sizeof(B)<<endl;
    cout<<"size of D "<<sizeof(D)<<endl;
    cout<<"size of C "<<sizeof(C)<<endl;
    }

结果是:

size of A 4
size of B 12
size of D 20
size of C 16

正如您在这个SO问题中看到的,在关于对象内存布局的标准中只定义了很少的内容。大部分布局是由实现定义的。当您提到MSVC实现时,出于好奇,让我们来看看这些(不可移植的)实现细节。

简单继承:

我建议您从没有虚拟功能的简单非虚拟继承开始:

class X {  };                     //    size of X 1 
class Y : public X { };           //    size of Y 1 

所有对象都必须具有非null大小(这仍然是标准大小)。这就是为什么我们看到1,尽管X是完全空的。注意,Y的大小与X相同,因为Y中没有添加任何内容(因此Y已经是非空大小)。

简单继承,具有虚拟函数:

当您开始使用虚拟函数时,MSVC会在对象的开头添加一个指向vtable的指针。这个SO问题展示了在简单继承的情况下vtable的原理。

class XF                              // size of XF 4
{ public: virtual void foo(){} };
class YF : public XF                  // size of YF 4
{ public: virtual void bar(){} };
class YX : public X                   // size of YX 4
{ public: virtual void bar(){} };       

您可以在这里看到vtable指针的大小是4。你可以立即推断出我是在32位模式下编译的,因为在x64模式下你会有8。

顺便说一句,为了多态性的目的,单个继承类的vtable被合并(在派生对象的开头只有一个vtable指针)。更多关于vtables的信息,请参阅这篇非常有趣的DDJ文章中的signle an multiple heritage。

没有虚拟继承的多重继承:

多重继承组合了所有继承的类,因此它的大小至少与继承类的大小相加一样大。

但由于对齐要求,它可能会更大,在虚拟函数的情况下,还有一个额外的vtable指针(合并不再像单一继承那样可能)。DDJ的文章也对此进行了解释。

虚拟继承:

让我们看看虚拟继承(与虚拟函数无关)的效果:

class ZX : public virtual X {   };     // size of ZX 4     
class ZF : public virtual XF {   };    // size of ZF 8 
class ZZF: public virtual ZF {   };    // size of ZZF 12

所以我们看到,无论何时进行虚拟继承,都会添加一个指针。这样做的目的是什么?

在没有虚拟固有性的情况下,从派生对象到其基础的上转换是直接的。

但对于虚拟继承,这种升级是一场噩梦,因为几个类共享同一个基类。因此,基类可能不再与派生类相邻。这就是虚拟继承需要指向虚拟基表的指针的原因对于每个虚拟继承。