virtual是唯一能从根本上解决菱形继承问题的机制,因其使d中只含一份a子对象并共享状态;普通多重继承导致d.func()因二义性编译失败,且b、c各自独立构造a造成冗余;虚继承必须声明在b、c对a的继承中,由最派生类d直接构造a,带来vbp和vbtable开销。

virtual 是唯一能从根本上解决菱形问题的机制,其他方式(如作用域限定)只是绕开二义性,不消除冗余。
为什么普通多重继承会导致 d.func() 编译失败
当 B 和 C 都 public 继承 A,而 D 同时继承 B 和 C 时,D 对象内存中实际包含两份 A 的子对象。编译器无法判断 d.func() 应该调用 B::A::func() 还是 C::A::func(),直接报错:error: request for member 'func' is ambiguous。
此时即使写成 d.B::func() 或 d.C::func() 能通过编译,但 D 仍占用双倍 A 的数据空间,且两个 A 实例状态完全独立——改一个不影响另一个,极易引发逻辑错误。
virtual 必须加在中间层,不是顶层也不是底层
虚继承的声明位置非常关键:它必须出现在 B 和 C 对 A 的继承声明中,而不是 D 对 B、C 的继承里。
- ✅ 正确:
class B : virtual public A {};和class C : virtual public A {}; - ❌ 错误:
class D : virtual public B, public C {};—— 这对解决菱形问题毫无作用 - ❌ 错误:
class A { virtual ~A() = default; };—— 虚函数和虚继承无关
virtual 关键字修饰的是“继承关系”,不是类本身;A 在此语境下被称为虚基类(virtual base class),但它自身无需任何修改。
构造顺序变了,A 的构造函数由 D 直接调用
启用虚继承后,构造逻辑不再是“A → B → C → D”,而是:A 的构造函数由最派生类 D 直接负责调用,B 和 C 的构造函数中对 A 的初始化会被忽略。
这意味着:
- 如果
A的构造函数有参数,D的构造函数初始化列表必须显式调用A(...) -
B和C的构造函数里写A(42)是无效的,编译器会静默丢弃 - 若遗漏对虚基类的初始化,会触发编译错误:
call to implicitly-deleted default constructor of 'A'
例如:class D : public B, public C { D() : A(100), B(), C() {} }; —— A(100) 这一节不可省略。
虚继承带来内存与性能开销,别滥用
虚继承不是免费的午餐。每个使用虚继承的类对象会额外携带一个或多个虚基类指针(vbp),并依赖虚基类表(vbtable)做偏移计算。这导致:
- 对象尺寸增大(尤其在嵌入式或内存敏感场景需警惕)
- 成员访问速度略慢(需查表+偏移计算,而非直接地址偏移)
- 类型转换受限:
D*不能隐式转为A*,必须用static_cast或dynamic_cast
只有当你真正面临菱形结构且需要共享状态时才用虚继承。若只是接口聚合(如 class Widget : public Drawable, public Clickable),且 Drawable 和 Clickable 没有共同父类,就完全不需要 virtual。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











