#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。请清楚地解释一下这个虚拟继承是如何工作的。
答案 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:来自ClassB
和ClassC
。在ClassD
对象中,两个vptr都会指向某些 ClassD
vtable,但同样的ClassD
vtable:
ClassB
主题指向ClassB-in-ClassD vtable,表示ClassA
基础ClassB
基础的相对位置ClassC
主题指向ClassC-in-ClassD vtable,它指示ClassA
基础ClassC
基础的相对位置我猜你的问题是:我们需要两个不同的vptr吗?
从技术上讲,有时可以通过覆盖基类子对象来优化类的大小。这是技术上可行的情况之一:
覆盖(或统一)意味着ClassB
和ClassC
将共享相同的vptr:给定d
ClassD
的实例:
&d.ClassB::vptr == &d.ClassC::vptr
d.ClassB::vptr == d.ClassC::vptr
但d.ClassB::vptr == &ClassC_in_ClassD_vtable
和d.ClassC::vptr == &ClassC_in_ClassD_vtable
,ClassB_in_ClassD_vtable
必须与ClassC_in_ClassD_vtable
统一。在这种特殊情况下,ClassB_in_ClassD_vtable
和ClassC_in_ClassD_vtable
仅用于描述ClassA
子对象的偏移量;如果在ClassB
中统一了ClassC
和ClassD
个子对象,那么这些偏移也是统一的,因此可以统一vtable。
请注意,这是唯一可行的,因为它有完美的相似性。如果修改ClassB
和ClassC
以在每个虚拟函数中添加一个虚函数,例如这些虚函数不等效(因此不可统一),则无法实现vtable统一。
这种优化只能在非常简单的情况下才能实现。这些情况不是典型的C ++编程:人们通常将虚拟基类与虚函数结合使用。空基类优化很有用,因为许多C ++习惯用法都使用没有数据成员或虚函数的基类。 OTOH是一种用于特殊使用虚拟基类的微小(一个vptr)空间优化,对现实世界的程序似乎没有用。