虚拟继承中派生类的大小

时间:2013-03-11 09:31:03

标签: c++ sizeof virtual-inheritance memory-layout vptr

#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。请清楚地解释一下这个虚拟继承是如何工作的。

3 个答案:

答案 0 :(得分:2)

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

答案 1 :(得分:2)

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

答案 2 :(得分:0)

虚拟基类实现

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

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基础ClassB基础的相对位置
  • 一个ClassC主题指向ClassC-in-ClassD vtable,它指示ClassA基础ClassC基础的相对位置

可能的优化

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

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

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

请注意,这是唯一可行的,因为它有完美的相似性。如果修改ClassBClassC以在每个虚拟函数中添加一个虚函数,例如这些虚函数不等效(因此不可统一),则无法实现vtable统一。

结论

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