带有虚函数的虚继承的对象模型比较复杂,所以来整理一下。以下讨论均在visual studio2017以及64位环境的前提下进行;
1.带有虚函数的类
class A
{
public:
int a_;
virtual void foo1() { cout << "A1" << endl; }
virtual void foo2() { cout << "A2" << endl; }
virtual void foo3() { cout << "A3" << endl; }
};
对于这种情况,当然是8字节的虚函数表指针+4字节的int类型大小,默认对齐系数下对齐后的大小为16;(对齐系数可以通修改#pragma pack(k)中的k值来改变)
2.普通地继承带有虚函数的父类
class B:public A
{
public:
int b_;
virtual void foo1() override { cout << "B1" << endl; }
virtual void foo4() { cout << "B2" << endl; }
virtual void foo3() override { cout << "B3" << endl; }
};
在这种情况下,子类与父类公用虚函数表和虚函数表指针,子类override的函数直接覆盖父类虚函数表中同名函数的位置,子类当中多出的函数放在虚函数表的最后,该类的大小为:父类大小16+子类的int型变量4,默认对齐系数下对齐后得到结果为24;
3.虚继承下非完全覆盖父类的虚函数
class B:virtual public A
{
public:
int b_;
virtual void foo1() { cout << "B1" << endl; }
virtual void foo4() { cout << "B2" << endl; }
virtual void foo3() { cout << "B3" << endl; }
};
在虚继承的情况下,子类当中会有一个虚基类表指针,指向虚基类表,非完全覆盖下,子类会有自己的虚函数表指针,所以该类的大小为:A的大小16+虚函数表指针的大小8+虚基类指针的大小8+int型变量4,默认对齐系数下对齐后大小为40;
4.虚继承下完全覆盖父类的虚函数
class C:virtual public A
{
public:
int c_;
virtual void foo1() { cout << "C1" << endl; }
virtual void foo2() { cout << "C2" << endl; }
virtual void foo3() { cout << "C3" << endl; }
};
在完全覆盖的情况下,子类不再拥有自己的虚函数表指针(为什么做这样有别于非完全覆盖的设计现在还没有找到一个合理的答案,希望知道的大佬能告知一下。。),所以和情况3相比,该类只是少了一个虚函数指针,于是大小为40-8=32;
5.虚继承下的菱形问题
class Ori
{
public:
int a;
virtual void foo1() { cout << "Ori" << endl; }
};
class V1:virtual public Ori
{
public:
int v1;
virtual void foo2() { cout << "V1" << endl; }
};
class V2 :virtual public Ori
{
public:
int v2;
virtual void foo3() { cout << "V2" << endl; }
};
class V3 :public V1, public V2
{
public:
int v3;
virtual void foo4() { cout << "V3" << endl; }
};
类Ori的大小为16,类V1和V2都是40,对于类V3,因为虚继承机制,类Ori在V3中只有一个副本,尽管他从V1和V2间接继承了两次,所以V3的大小为:40+40-16(重复的A)+4,默认对齐系数下对齐后为72,函数foo4被加到类V1的虚函数表的最后;
6.多重虚继承下类的大小
class A
{
public:
int a_;
virtual void foo1() { cout << "A1" << endl; }
virtual void foo2() { cout << "A2" << endl; }
virtual void foo3() { cout << "A3" << endl; }
};//16
class B:virtual public A
{
public:
int b_;
virtual void foo1() { cout << "B1" << endl; }
virtual void foo4() { cout << "B2" << endl; }
virtual void foo3() { cout << "B3" << endl; }
};//40
class A2
{
public:
int a2_;
virtual void foo1() { cout << "A21" << endl; }
virtual void foo2() { cout << "A22" << endl; }
virtual void foo3() { cout << "A23" << endl; }
};//16
class C:public A2
{
public:
int c_;
virtual void foo1() { cout << "C1" << endl; }
virtual void foo2() { cout << "C2" << endl; }
virtual void foo3() { cout << "C3" << endl; }
};//24
class D :virtual public B,virtual public C
{
public:
virtual void foo1() { cout << "D1" << endl; }
virtual void foo2() { cout << "D12" << endl; }
virtual void foo3() { cout << "D13" << endl; }
virtual void foo10() { cout << "D13" << endl; }
};//80
由上面的规则,我们可以知道,类A的大小为16,类B的大小为40,类A2的大小为16,类C的大小为24,对于类D,由于foo10在前面的虚函数表当中都找不到,所以类D自己有一个虚函数表指针,当发生多重虚继承时,和多个虚函数一样,虚基类指针只有一个,编译器会通过偏移量去访问不同的虚基类,所以类D也只有一个虚基类指针,所以D的大小为:B的大小40+C的大小24+虚基类指针8+虚函数指针8=80。
7.多重继承下类的大小
class E :public B, public C
{
public:
virtual void foo1() { cout << "D1" << endl; }
virtual void foo2() { cout << "D12" << endl; }
virtual void foo3() { cout << "D13" << endl; }
virtual void foo10() { cout << "D13" << endl; }
};
相比于情况6,普通的多重继承少了一个虚基类表指针和一个虚函数表指针,虚函数表及虚函数表指针和会多重继承的第一个父类公用,覆盖的就直接覆盖,多出的就加到虚函数表最后,所以大小上为:80-8-8=64;
另外,关于这些数据在内存中的具体存放位置及规则,可以参考C++对象模型相关的部分。