虚基类在最终派生类中严格只有一份,由最派生类全权负责初始化,中间基类的构造调用被静默忽略;不加virtual且存在多条路径时必有两份子对象,导致二义性;虚继承引入vbptr和间接寻址,带来内存与性能开销。

虚基类在最底层派生类中只有一份
只要任一中间基类以 virtual 方式继承该基类,且最终派生类通过多条路径间接继承它,那么该虚基类的子对象在最终派生类实例中就严格只有 一份。这不是“最多一份”,而是编译器强制保证的唯一性。
为什么不是“看谁先声明”或“看谁显式调用构造函数”
虚基类的初始化由 最派生类(most derived class) 全权负责,无论中间有多少层虚继承,其构造函数必须在初始化列表中显式调用虚基类的构造函数。否则会编译失败(即使中间基类也写了调用,也会被忽略)。
-
A是虚基类,B : virtual public A,C : virtual public A,D : public B, public C - 只有
D的构造函数能决定用哪个A构造函数初始化那份唯一的A子对象 -
B和C构造函数里写的A(...)初始化语句会被编译器静默丢弃
不加 virtual 就一定有两份?
是的——但前提是存在多条继承路径。例如:
-
B : public A,C : public A,D : public B, public C→D对象内存中含两个独立的A子对象 - 此时访问
d.m_a会报错:request for member ‘m_a’ is ambiguous - 必须写成
d.B::m_a或d.C::m_a才能编译通过
这种二义性不是靠“using 声明”或“作用域解析”能绕开的——它反映的是底层确实存在两份数据。
立即学习“C++免费学习笔记(深入)”;
虚继承带来的布局和性能影响不能忽略
虚基类不是零成本抽象。为支持跨路径共享同一份子对象,编译器必须引入间接寻址机制:
- GCC 在对象头部插入
vbptr(虚基类指针),指向虚基类表vbtable - MSVC 可能将虚基类子对象移到对象末尾,并在内部维护偏移量
- 每次访问虚基类成员(如
d.value)都需一次指针解引用 + 偏移计算,比普通继承慢一点点 - 对象大小通常增大(至少多一个指针宽度),且内存布局不再连续可预测
所以,只在真正需要消除菱形歧义时才用虚继承;不要为了“看起来更干净”而滥用。


















