C++虚继承下带有虚函数的类的大小(含多重虚继承,虚继承后完全覆盖等复杂情况)

本文深入探讨了C++中带有虚函数的虚继承的对象模型,包括虚继承下的对象大小、菱形继承问题、多重虚继承和多重继承的情况。通过对不同场景的详细解析,帮助读者理解虚继承和虚函数的实现机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

带有虚函数的虚继承的对象模型比较复杂,所以来整理一下。以下讨论均在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++对象模型相关的部分。

### 虚继承的概念 在C++中,虚继承是一种机制,用于解决继承中的菱形问题(Diamond Problem)。当一个通过个路径继承同一个基时,可能会导致重复的基实例。为了避免这种冗余并确保只有一个基实例被共享,可以使用虚继承[^1]。 虚继承的关键在于编译器会创建一种特殊的指针调整机制来处理对象模型的变化。通常情况下,这涉及到虚拟函数表(vtable),它不仅存储了每个虚函数的地址,还包了`virtual_delta`——即为了转换调用者提供的`this`指针到实际所需的`this`指针所要加上的字节数量。 ### 使用方法与示例 以下是关于如何定义和实现带有虚继承的一个简单例子: ```cpp #include <iostream> using namespace std; class Base { public: void show() { cout << "Base Class\n"; } }; // A 和 B 都从 Base 继承 class A : virtual public Base {}; class B : virtual public Base {}; // Derived 同时从 A 和 B 继承 class Derived : public A, public B {}; int main() { Derived d; d.show(); // 只有一个 Base 型的对象存在 } ``` 在这个程序里,无论 `Derived` 是经由 `A` 还是 `B` 来访问 `Base` 的成员函数 `show()`,都只会有一份 `Base` 实例存在于最终派生出来的对象之中。这是因为我们在声明 `A` 和 `B` 时均采用了关键字 `virtual`。 另外,在某些场景下,接口的设计也会利用抽象基配合纯虚函数的形式完成。比如下面这个简单的接口设计案例展示了如何构建仅虚函数的方法集合作为接口标准[^2]: ```cpp class IShape { public: virtual ~IShape() {} // Virtual destructor is important. virtual double area() const = 0; // Pure virtual function defining contract. }; ``` 这里定义了一个名为 `IShape` 的接口,任何实现了该接口的具体形状都需要提供自己的面积计算逻辑。注意这里的析构函数也应该是虚的以便安全释放资源。 ### 总结 综上所述,虚继承主要用于防止多重继承带来的二义性和数据复制问题;而接口则可以通过完全由未实现的纯虚函数构成的方式来表达特定的行为契约。两者都是面向对象编程的重要组成部分,并且各自有其适用场合以及最佳实践方式。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值