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


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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
C++中的多态性是通过虚函数实现的。在含有虚函数的中,编译器会自动添加一个指向虚函数表的指针,这个指针通常称为虚函数表指针。虚函数表是一个存储的虚函数地址的数组,每个有一个对应的虚函数表。当一个对象被创建时,会自动分配一个指向它的虚函数表的指针。 虚函数表指针的大小和虚函数表的大小都与具体实现相关。在一般情况下,虚函数表指针的大小为4或者8个字节,虚函数表的大小取决于中虚函数的个数。 以下是一个模拟实现: ```c++ #include <iostream> using namespace std; class A { public: virtual void func1() { cout << "A::func1" << endl; } virtual void func2() { cout << "A::func2" << endl; } }; class B : public A { public: virtual void func1() { cout << "B::func1" << endl; } }; int main() { A* a = new A(); B* b = new B(); cout << "size of A: " << sizeof(A) << endl; cout << "size of B: " << sizeof(B) << endl; cout << "size of a: " << sizeof(a) << endl; cout << "size of b: " << sizeof(b) << endl; a->func1(); a->func2(); b->func1(); b->func2(); delete a; delete b; return 0; } ``` 输出结果: ``` size of A: 8 size of B: 8 size of a: 8 size of b: 8 A::func1 A::func2 B::func1 A::func2 ``` 在上面的代码中,我们定义了两个A和B,其中B继承自A。A和B都含有虚函数,因此编译器会为它们添加虚函数表指针。在main函数中,我们创建了一个A对象和一个B对象,并输出了它们的大小以及指针的大小。接着我们调用了每个对象的虚函数,可以看到B对象的func1()覆盖了A对象的func1(),而A对象的func2()没有被覆盖。最后我们删除了这两个对象,避免内存泄漏。 需要注意的是,虚函数表指针的大小和虚函数表的大小是不确定的,取决于具体实现。此外,虚函数表指针通常被放在对象的开头,因此虚函数表通常被放在内存中较靠前的位置。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值