继承是C++面向对象的核心特性之一,说明类与类之间的特性是可以继承的,这大大提高了代码的复用性,优化了程序结构。但是滥用继承也会导致菱形继承的多继承问题。
菱形继承
什么是菱形继承呢?指一个派生类同时继承两个直接基类,这两个直接基类又继承自同一个间接基类,最终形成 "菱形" 的继承结构。

下面用代码展示菱形继承的结构示例:
cpp
// 顶层基类
class A {
public:
int a;
A(int val) : a(val) {}
};
// 中间基类 B,继承 A
class B : public A {
public:
B(int val) : A(val) {}
};
// 中间基类 C,继承 A
class C : public A {
public:
C(int val) : A(val) {}
};
// 最终派生类 D,同时继承 B 和 C
class D : public B, public C {
public:
// 问题1:初始化 A 时,B 和 C 都会分别初始化 A,导致 A 被初始化两次
D(int val1, int val2) : B(val1), C(val2) {}
};
int main() {
D d(1, 2);
// 问题2:访问 a 时,编译器无法确定是 B::A::a 还是 C::A::a,直接报错
// cout << d.a << endl;
// 必须显式指定,但这违背了"单一继承"的逻辑,且数据冗余(d 中有两个 a)
cout << d.B::a << endl; // 输出 1
cout << d.C::a << endl; // 输出 2
return 0;
}
上述问题中,A为顶级基类,B和C继承A,初始化 A 时,B 和 C 都会分别初始化 A,导致 A 被初始化两次;访问 a 时,编译器无法确定是 B::A::a 还是 C::A::a,直接报错
注:"B::A::a"的含义是有两层:
"A::a"表示 "类
A中的成员变量a""B::"
B是A的派生类
核心问题
菱形继承的核心问题是间接基类的成员会被多次复制,导致数据冗余、二义性,甚至逻辑错误。
数据冗余表现在间接基类 A 的成员因为B和C的缘故会在最终派生类 D 中存在两份,浪费内存;
二义性则表现在直接访问 D 对象的 A 成员时,编译器无法区分是 B 继承的 A 还是 C 继承的 A,就会造成编译报错;
逻辑错误:若 A 有虚函数,多态调用时可能因重复的基类指针导致行为异常。原因是非虚继承的菱形结构中,最终派生类会包含两份 A 的虚指针(vptr),多态调用时无法确定该用哪一个,导致调用结果不符合预期,甚至崩溃。
菱形继承的内存布局如下图所示:

解决方案:虚继承
由于多继承会造成菱形继承问题,那么C++ 提供虚继承 机制就是解决菱形继承的办法。虚继承通过让中间基类(B、C)共享同一个间接基类(A)的实例,从而消除数据冗余和二义性
在中间基类继承顶层基类时,添加 virtual 关键字,用代码举例如下:
cpp
// 顶层基类(不变)
class A {
public:
int a;
A(int val) : a(val) {}
};
// 中间基类 B:虚继承 A
class B : virtual public A {
public:
// 虚继承下,B 的构造函数不再直接初始化 A(A 的初始化由最终派生类负责)
B() {}
};
// 中间基类 C:虚继承 A
class C : virtual public A {
public:
C() {}
};
// 最终派生类 D:必须直接初始化虚基类 A
class D : public B, public C {
public:
// 核心:虚基类 A 的构造由最终派生类 D 统一初始化,避免重复
D(int val) : A(val), B(), C() {}
};
int main() {
D d(10);
// 无歧义:d 中只有一份 A::a
cout << d.a << endl; // 输出 10
cout << d.B::a << endl; // 仍可显式访问,结果同上
cout << d.C::a << endl; // 结果同上
return 0;
}
通过上述代码,我们深入分析虚继承的底层原理,虚继承是通过虚基类表(vbtable) 和虚基类指针(vbptr) 实现的:
中间基类(
B、C)的对象中会增加一个vbptr指针,指向虚基类表;虚基类表存储当前对象到虚基类(
A)实例的偏移量;最终派生类(
D)中只保留一份A的实例,B和C的vbptr都指向这同一个实例。
非虚继承的内存布局示意图如下所示:
虚继承的内存布局示意图如下所示: 
虽然虚继承可以解决菱形继承的问题,但在现实开发中,为了减少不必要的麻烦,尽量避免使用多继承。
接口多继承的安全场景
若顶层基类是纯虚类,即使是菱形继承结构,也无数据冗余(因为纯虚类无成员变量),此时无需虚继承,用代码举例如下:
cpp
// 纯虚接口 A
class A {
public:
virtual void func() = 0;
virtual ~A() = default;
};
class B : public A {
public:
void func() override { cout << "B::func" << endl; }
};
class C : public A {
public:
void func() override { cout << "C::func" << endl; }
};
class D : public B, public C {
public:
// 必须重写 func,否则 D 仍是抽象类(解决二义性)
void func() override { B::func(); }
};
int main() {
D d;
d.func(); // 输出 B::func,无歧义
return 0;
}
总结
菱形继承的核心问题是间接基类成员重复,虚继承通过共享基类实例解决该问题,实际开发中应优先避免多继承。
以上就是本文的所有内容,如果本文对你有帮助的话欢迎点赞收藏哦~
感兴趣的朋友也欢迎关注哟我将会持续输出编程开发的内容