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

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

一句话总结

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

相关推荐
油炸自行车15 小时前
【Qt】bug排查笔记——QMetaObject::invokeMethod: No such method
c++·笔记·qt·bug
qq_3928079516 小时前
C++ 条件变量,互斥锁
c++
天若有情67316 小时前
C++革命性新特性:默认实例导出(exportDefault)让单例模式变得无比简单!
c++·后端
科大饭桶18 小时前
C++入门自学Day17-- 模版进阶知识
c语言·开发语言·c++·容器
91刘仁德18 小时前
c++ 类和对象(上)
开发语言·c++·经验分享·笔记·算法
阿捏利21 小时前
C++ Primer Plus 第六版 第二章 编程题
c++·编程题·c++ primer plus
郝学胜-神的一滴1 天前
Pomian语言处理器研发笔记(二):使用组合模式定义表示程序结构的语法树
开发语言·c++·笔记·程序人生·决策树·设计模式·组合模式
青瓦梦滋1 天前
Linux基本工具(yum、vim、gcc、Makefile、git、gdb)
linux·运维·服务器·c++
qq_433554541 天前
C++ Bellman-Ford算法
开发语言·c++·算法