什么时候需要使用虚继承,什么是菱形继承

在阅读ROS的源码的时候,遇到了这样的一个地方:

cpp 复制代码
/** \brief %Controller with a specific hardware interface
 *
 * \tparam T The hardware interface type used by this controller. This enforces
 * semantic compatibility between the controller and the hardware it's meant to
 * control.
 */
template <class T>
class Controller: public virtual ControllerBase
{
public:

这个里面继承类有一个virtual关键字,这个virtual关键字的作用是什么呢,什么时候会需要这样的一个virtual关键字呢,本篇文章将回答这些问题:


背景

在 ROS 控制框架(尤其是 ros_control)中,控制器类通常会继承多个基类,这些基类可能最终都继承自同一个公共基类(例如 ControllerBase)。这就会遇到菱形继承(diamond inheritance) 的问题。

普通继承下的问题

假设有以下继承关系:

复制代码
ControllerBase
   ^         ^
   |         |
ControllerA  ControllerB
       \     /
        \   /
   MyController

如果 ControllerAControllerB 都是普通继承 ControllerBase

cpp 复制代码
class ControllerA : public ControllerBase { ... };
class ControllerB : public ControllerBase { ... };

class MyController : public ControllerA, public ControllerB { ... };

那么 MyController 内部会有两份 ControllerBase 子对象,这会带来:

  • 二义性访问:MyController 中访问 ControllerBase 成员时编译器会报错(不知道选哪一份)。
  • 多份数据冗余:ControllerBase 的状态被复制两份,很可能导致逻辑混乱。

虚拟继承(virtual)的作用

虚拟继承的目的是在多重继承场景下,让所有派生类共享一份公共基类实例,避免菱形继承中的重复子对象。

如果这样改:

cpp 复制代码
class ControllerA : public virtual ControllerBase { ... };
class ControllerB : public virtual ControllerBase { ... };

class MyController : public ControllerA, public ControllerB { ... };

那么不管你通过 ControllerA 还是通过 ControllerB 去访问 ControllerBase,都会访问到同一份实例,不会重复创建。


回到你的例子

ros_control 框架中,Controller 模板类是所有具体控制器的一个中间基类,它通常可能和其他基类(比如插件接口、硬件接口适配器等)一起被多重继承,并且这些基类的共同祖先是 ControllerBase

如果 Controller 不使用 virtual 继承 ControllerBase,一旦你有另外一个基类也继承了 ControllerBase,那么最终你的实际控制器类就会陷入菱形继承问题。

因此,设计者直接在 Controller 这里用 public virtual ControllerBase预防将来在多重继承中出现菱形继承的问题


总结

virtual 在这里的作用是:

  1. 解决/预防菱形继承 导致的重复基类子对象问题。
  2. 保证唯一性 :所有继承链里只保留一份 ControllerBase 成员。
  3. 提高兼容性与健壮性:即使未来添加新的中间类,也不会造成二义性访问。

一句话总结

因为 ros_control 框架的控制器体系存在多重继承的可能性,Controller 类用 virtual 继承 ControllerBase 是为了让最终派生类只保留一份 ControllerBase 实例,避免菱形继承带来的二义性与数据重复。


那为什么不所有的继承都使用虚拟继承呢?

这个问题很关键,很多人第一次学 virtual inheritance 的时候也会想------既然解决了菱形继承的问题,那干脆所有继承都写成虚拟继承是不是更安全?

答案是:不应该所有继承都用虚拟继承,原因主要有四点。


1. 虚拟继承有额外开销(复杂度 + 性能)

  • 普通继承时,基类子对象位置在编译期固定在派生类对象的内存布局中,访问基类成员只需要固定偏移量
  • 虚拟继承为了支持"唯一基类",编译器会引入 虚基表(vbase table) 来在运行时计算基类子对象真实位置。
  • 结果:
    • 内存布局更复杂,访问虚基类成员速度可能略慢(需要多一次指针间接跳转)。
    • 对象大小一般会增加(多一个指向虚基表的指针)。

2. 虚拟继承降低可读性和直观性

  • 普通继承:构造函数按照继承链自动初始化基类对象。
  • 虚拟继承:
    • 最终派生类 必须负责虚基类的初始化,即使中间基类也定义了初始化逻辑,这会让初始化代码更分散。
    • 容易让新手迷惑"这个基类的构造为什么不能在中间类里调用生效?"

例如:

cpp 复制代码
struct A {
    A(int x) { ... }
};
struct B : virtual A {
    B() : A(1) {} // 这里并不会真正构造 A
};
struct C : virtual A {};
struct D : B, C {
    D() : A(42) {} // 最终派生类必须管理 A 的构造
};

B 中初始化 A(1) 是无效的,因为虚基类初始化由 D 决定。


3. 虚拟继承不必要时反而制造麻烦

  • 如果类层次很简单,没有菱形继承或公共基类重复问题,虚拟继承不会带来好处,反而增加了内存间接访问、初始化复杂度。
  • 而且虚拟继承使得类设计绑定得更紧,对编译器和链接器都增加额外工作量。

4. 虚拟继承是为了特殊场景而设计的

  • 它的主要用途就是解决多重继承中公共基类的重复 ,比如:
    • 接口类(多重继承常见)
    • 框架基类(如 ControllerBase
  • 在常规一条继承链中(单继承),虚拟继承是完全没有意义的。

在 ROS ros_control 里的设计哲学

  • ControllerBase 是公共框架基类,有可能在多重继承中被重复继承 → 用虚拟继承。
  • 其他不需要多重继承的类就用普通继承,保持简单、高效。
  • 这样只在必要的地方引入虚拟继承,避免全局复杂化。

一句话总结

虚拟继承是为了解决菱形继承的特殊工具,不是通用继承方式。它带来额外的运行和设计复杂度,因此只有在可能出现重复基类的情况下才使用,比如框架核心接口类。

相关推荐
西阳未落2 小时前
C++基础(21)——内存管理
开发语言·c++·面试
超级大福宝2 小时前
使用 LLVM 16.0.4 编译 MiBench 中的 patricia遇到的 rpc 库问题
c语言·c++
wangjialelele2 小时前
Linux中的线程
java·linux·jvm·c++
hsjkdhs4 小时前
万字详解C++之构造函数析构函数
开发语言·c++
SELSL5 小时前
SQLite3的API调用实战例子
linux·数据库·c++·sqlite3·sqlite实战
什么半岛铁盒5 小时前
C++项目:仿muduo库高并发服务器-------Channel模块实现
linux·服务器·数据库·c++·mysql·ubuntu
闭着眼睛学算法5 小时前
【华为OD机考正在更新】2025年双机位A卷真题【完全原创题解 | 详细考点分类 | 不断更新题目 | 六种主流语言Py+Java+Cpp+C+Js+Go】
java·c语言·javascript·c++·python·算法·华为od
ShineSpark5 小时前
C++面试11——指针与引用
c++·面试
杨小码不BUG6 小时前
CSP-J/S初赛知识点精讲-图论
c++·算法·图论··编码·csp-j/s初赛
初圣魔门首席弟子6 小时前
flag使用错误出现bug
c++·bug