C++虚继承下的类大小

前言

带有虚函数的虚继承的对象模型比较复杂,所以单独整理一下。其实关于计算类大小是C++的一大易错点之一。即便是我在这儿理了半天,也不一定就是正确的,如果大佬看到本文,发现我很多错误而又肯评论赐教的话,感激不尽。

不多说了(讨厌C++的原因更多了)

此文可以配合《C++中#pragma pack(N)计算sizeof》一起看。

以下讨论在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的函数直接覆盖父类虚函数表中同名函数的位置,子类当中多出的函数放在虚函数表的最后,该类的大小为:虚指针8+父int4+子int4,默认对齐系数下对齐后得到结果为16;

3.虚继承下非完全覆盖父类的虚函数

class BB:virtual public A
{
public:
	int bb_;
	virtual void foo1() { cout << "BB1" << endl; }
	virtual void foo4() { cout << "BB2" << endl; }
	virtual void foo3() { cout << "BB3" << endl; }
};

在虚继承的情况下,子类当中会有一个虚基类表指针,指向虚基类表,非完全覆盖下,子类会有自己的虚函数表指针,所以该类的大小为:BB虚函数表指针8+虚基类指针8+BB的int变量4+偏移4+A的虚函数表指针8+A的int变量4+偏移4,默认对齐系数下对齐后大小为40;(在C++对象模型中,虚继承而来的派生类会生成一个隐藏的虚基类指针(vbptr),在Microsoft Visual C++中,虚基类表指针总是在虚函数表指针之后)

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; }
};

这里需要先说,内存布局的时候,依次是左一、左二...,也就是先V1,再V2,再是虚爷爷Ori。

类Ori的大小为16,类V1和V2都是40,对于类V3,因为虚继承机制,类Ori在V3中只有一个副本,尽管他从V1和V2间接继承了两次,所以V3的大小为:(V1虚函数表指针8+虚基类指针8+V1的int变量4)+(V2虚函数表指针8+虚基类指针8+V2的int变量4)+(V3的int变量4+偏移量4)+(A的虚函数表指针8+A的int变量4+偏移量4),默认对齐系数下对齐后为64,函数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; }
};//16
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; }
};

由上面的规则,我们可以知道,类A的大小为16,类B的大小为40,类A2的大小为16,类C的大小为16,对于类D,由于foo10在前面的虚函数表当中都找不到,所以类D自己有一个虚函数表指针,当发生多重虚继承时,和多个虚函数一样,虚基类指针只有一个,编译器会通过偏移量去访问不同的虚基类,所以类D也只有一个虚基类指针,因此D的大小为:D虚函数表指针8+虚基类指针8+B的大小+C的大小 = 72;

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,普通的多重继承少了一个虚基类表指针和一个虚函数表指针,虚函数表会多重继承的第一个父类公用,覆盖的就直接覆盖,多出的就加到虚函数表最后,所以大小上为:(B虚函数表指针8+虚基类指针8+B的int变量4)+(C虚函数表指针8+C的int变量4)+(A的虚函数表指针8+A的int变量4+偏移量4)= 48


我看有的文章,直接继承是把父类的大小加子类的变量。我感觉应该不是这样吧,直接继承的时候,父类的偏移量应该不算吧。具体我也不知道,先记录到这里,后面我去看看内存布局。

猜你喜欢

转载自blog.csdn.net/Jason_Lee155/article/details/130286718