由于C++支持多继承,除了public、protected和private三种继承方式外,还支持虚拟(virtual)继承,举个例子:
#include <iostream>
using namespace std;
class A {};
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};
int main()
{
cout << "sizeof(A):" << sizeof A << endl; // 1,空对象,只有一个占位
cout << "sizeof(B):" << sizeof B << endl; // 4,一个bptr指针,省去占位,不需要对齐
cout << "sizeof(C):" << sizeof C << endl; // 4,一个bptr指针,省去占位,不需要对齐
cout << "sizeof(D):" << sizeof D << endl; // 8,两个bptr,省去占位,不需要对齐
}
/*
sizeof(A):1
sizeof(B):8
sizeof(C):8
sizeof(D):16
*/
上述代码所体现的关系是,B和C虚拟继承A,D又公有继承B和C,这种方式是一种菱形继承或者钻石继承,可以用如下图来表示:
虚拟继承的情况下,无论基类被继承多少次,只会存在一个实体。**虚拟继承基类的子类中,子类会增加某种形式的指针,或者指向虚基类子对象,或者指向一个相关的表格;表格中存放的不是虚基类子对象的地址,就是其偏移量,此类指针被称为bptr,如上图所示。如果既存在vptr又存在bptr,某些编译器会将其优化,合并为一个指针。
虚拟继承是一种多重继承的特殊形式,它的目的是解决菱形继承中的冗余数据和二义性问题¹。
菱形继承是指一个派生类继承了两个或多个直接基类,而这些直接基类又继承自同一个间接基类,形成了一个菱形的继承结构²。例如:
class A {
// 间接基类
};
class B: public A {
// 直接基类
};
class C: public A {
// 直接基类
};
class D: public B, public C {
// 派生类
};
在这种情况下,派生类 D 中会包含两份间接基类 A 的成员,一份来自 B,一份来自 C。这样会造成以下问题³:
- 冗余数据:派生类 D 中的两份 A 的成员占用了多余的内存空间,而且可能存储了不一致的数据。
- 二义性:当派生类 D 中访问 A 的成员时,编译器无法确定是访问 B 中的 A 还是 C 中的 A,需要显式地指定路径,否则会报错。
为了解决这些问题,可以使用虚拟继承,即在继承方式前加上 virtual 关键字,表示该基类是一个虚拟基类,例如:
class A {
// 间接基类
};
class B: virtual public A {
// 直接基类
};
class C: virtual public A {
// 直接基类
};
class D: public B, public C {
// 派生类
};
这样,派生类 D 中就只保留了一份间接基类 A 的成员,不会出现冗余数据和二义性问题。虚拟继承的原理是通过虚基指针和虚基表来实现的,每个包含虚拟基类的类都有一个虚基指针,指向一个虚基表,虚基表中存放了虚拟基类的偏移量,用于定位虚拟基类的位置⁴。
虚拟继承的作用是在多重继承的情况下,保证间接基类的实例只会在派生类对象中存在一次,而不会重复出现。这样可以避免数据的冗余和访问的二义性,也可以节省内存空间和提高效率。虚拟继承是一种复杂的继承方式,一般不推荐使用,除非在必要的情况下。多重继承本身就容易引起设计上的混乱和错误,所以在实际开发中应该尽量避免使用多重继承,或者使用单一继承和组合的方式来代替多重继承。