虚拟继承中派生类的大小

Posted

技术标签:

【中文标题】虚拟继承中派生类的大小【英文标题】:size of derived class in virtual inheritance 【发布时间】:2013-02-26 10:09:42 【问题描述】:
#include "stdafx.h"
#include <iostream>
using namespace std;

class ClassA

    protected:
       int width, height;
    public:
       void set_values(int x, int y)
       
         width = x;
         height = y;
       
;

class ClassB : virtual public ClassA

   //12(int + int + vptr)
;

class ClassC : virtual public ClassA

  //12(int + int + vptr)
;

class ClassD : public ClassB, public ClassC

;

int main()

  ClassA A;
  ClassB B;
  ClassC C;
  ClassD D;
  cout << "size = " << sizeof(A) << endl;
  cout << "size = " << sizeof(B) << endl;
  cout << "size = " << sizeof(C) << endl;
  cout << "size = " << sizeof(D) << endl;
  return 0;

我得到的输出是:

size of ClassA = 8
size of ClassB = 12
size of ClassC = 12
size of ClassD = 16

在上面的代码中,为什么 ClassD 的输出是 16。请清楚地解释一下这个虚拟继承是如何工作的。

【问题讨论】:

这取决于实现。无论如何,您为什么要依靠大小来确定特定的东西?如果您需要尺寸,只需使用sizeof,它会返回适当的尺寸。出于理论目的知道这一点很好,但这绝对没有实际应用。 int + int + 2 * vptr? 只是为了了解这个虚拟继承。根据我的说法,ClassD 的大小应该是 24 字节,即 (int + int + vptr) + (int + int + vptr) = 24 bytes 。所以我很困惑这是如何工作的所以问.. @Jon:虽然这里没有虚函数,所以应该不需要任何 vptrs,AFAICS。 @OliCharlesworth 需要 vtable 才能定位 ClassA 子对象。 【参考方案1】:

虚拟继承意味着虚拟基类只存在一次,而不是多次。这就是为什么来自ClassA 的8 个字节只在ClassD 中出现一次。虚拟继承本身需要一定的开销,因此您会得到一个额外的指针。 C++ 标准没有指定确切的实现以及因此的确切开销,并且可能会根据您创建的层次结构而有所不同。

【讨论】:

"12 个字节来自" 你的意思是 8 个字节吗?【参考方案2】:

当 ClassD 继承 ClassB 和 ClassC 时,将有两个 vptr(一个来自 B,一个来自 C)。 Scott Meyers 的“更有效的 C++”第 24 条(各种语言特性的成本)中描述了这种确切的情况。

【讨论】:

【参考方案3】:

虚拟基类实现

虚拟基类与虚拟函数完全一样:它们的地址(或相对地址,也就是偏移量)在编译时是未知的:

void f(ClassB *pb) 
    ClassA *pa = pb;

这里编译器必须计算ClassA 基本子对象与ClassB 子对象(或主要是派生对象)的偏移量。一些编译器只是在ClassB 中有一个指向它的指针;其他人使用 vtable,就像虚拟功能一样。

在这两种情况下,ClassB 的开销都是一个指针。

ClassC 类似,但 vptr 将指向 ClassC vtable,而不是 ClassB vtable。

因此,ClassD 对象将包含(这不是有序列表):

单个ClassA 子对象 ClassB 主题 ClassC 主题

所以ClassD 有两个继承的vptr:来自ClassBClassC。在ClassD 对象中,两个vptr 都将指向一些 ClassD vtable,但相同的ClassD vtable:

ClassB 主题指向 ClassB-in-ClassD vtable,表示 ClassA base 与 ClassB base 的相对位置 一个ClassC主题指向ClassC-in-ClassD vtable,表示ClassAbase与ClassCbase的相对位置

可能的优化

我猜你的问题是:我们需要两个不同的 vptr 吗?

从技术上讲,有时可以通过覆盖基类子对象来优化类的大小。这是技术上可行的情况之一:

重叠(或统一)意味着ClassBClassC 将共享相同的vptr:给定d 一个ClassD 的实例: &amp;d.ClassB::vptr == &amp;d.ClassC::vptr 所以d.ClassB::vptr == d.ClassC::vptrd.ClassB::vptr == &amp;ClassC_in_ClassD_vtabled.ClassC::vptr == &amp;ClassC_in_ClassD_vtable,所以ClassB_in_ClassD_vtable 必须与ClassC_in_ClassD_vtable 统一。在这种特殊情况下,ClassB_in_ClassD_vtableClassC_in_ClassD_vtable 都只用于描述ClassA 子对象的偏移量;如果ClassBClassC 子对象在ClassD 中统一,那么这些偏移量也是统一的,因此vtable 的统一是可能的。

请注意,这仅在此处是可能的,因为它们完全相似。如果 ClassBClassC 被修改为在每个中添加一个虚函数,例如这些虚函数不等价(因此不可统一),则 vtable 统一是不可能的。

结论

这种优化只有在像这样的非常简单的情况下才有可能。这些情况不是 C++ 编程的典型情况:人们通常将虚拟基类与虚拟函数结合使用。空基类优化很有用,因为许多 C++ 习惯用法使用没有数据成员或虚函数的基类。 OTOH,针对虚拟基类的特殊用途的微小(一个 vptr)空间优化似乎对现实世界的程序没有用。

【讨论】:

以上是关于虚拟继承中派生类的大小的主要内容,如果未能解决你的问题,请参考以下文章

C++中派生类的构造函数怎么显式调用基类构造函数?

在多级继承中派生的虚拟基类会发生啥?

C++中派生类重写基类重载函数时需要注意的问题:派生类函数屏蔽基类中同名函数

C ++中派生类的相等性测试[重复]

深入浅出之C++中的继承

多重继承,虚继承,MI继承中虚继承中构造函数的调用情况