虚继承中派生类的大小

size of derived class in virtual inheritance

本文关键字:派生 继承      更新时间:2023-10-16
#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。请给我解释清楚这个虚拟继承是如何工作的。

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

当ClassD继承ClassB和ClassC时,将会有两个vptrs(一个来自B,一个来自C)。这种确切的情况在Scott Meyers的"更有效的c++"第24项(各种语言特性的代价)中有描述。

虚拟基类实现

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

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

这里编译器必须计算ClassA基子对象与ClassB子对象(或大部分派生对象)的偏移量。有些编译器只是在ClassB内部有一个指向它的指针;其他使用虚函数表,就像虚函数一样。

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

ClassC是类似的,但是vptr将指向ClassC虚表,而不是ClassB虚表。

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

  • 单个ClassA子对象
  • a ClassB subject
  • a ClassC subject

所以ClassD有两个继承的vptr:来自ClassBClassC。在ClassD对象中,两个vptr都指向某个 ClassD虚表,但指向同一个ClassD虚表:

  • a ClassB主题指向ClassB-in-ClassD虚表,表示ClassA基与ClassB
  • 的相对位置。
  • a ClassC主题指向ClassC-in-ClassD虚表,表示ClassA碱基与ClassC碱基的相对位置

可能的优化

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

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

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

注意,这只可能在这里,因为有完美的相似性。如果对ClassBClassC进行修改,在它们各自中添加一个虚函数,比如这些虚函数不相等(因此不统一),那么虚函数表就不可能统一。

结论

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