于 C++ 的虚函数多态 和 模板方法模式 的结合

要理解 LaneChangePath 如何 "调用父类 Execute 却返回自己的 Process",核心在于 C++ 的虚函数多态模板方法模式 的结合 ------ 父类 PathGeneration 定义了固定的执行流程(Execute),子类 LaneChangePath 只需重写流程中的 "可变步骤"(Process),即可在复用父类流程的同时,定制自己的核心逻辑。以下从 "类继承关系""语法细节""调用流程" 三方面拆解:

一、先理清类的继承关系

首先明确代码中 3 个类的层级(核心是 "父类定义框架,子类定制细节"):

类名 继承关系 核心作用
Task 最顶层基类 定义规划任务的抽象接口(如 Execute 方法),是所有规划任务的统一基类。
PathGeneration 继承 Task 作为 "路径生成类" 的中间基类,封装固定的执行流程Execute 方法),并将 "可变的核心逻辑" 交给虚函数 Process
LaneChangePath 继承 PathGeneration 具体的 "换道路径生成类",不重写 Execute (复用父类流程),只重写 Process(实现换道路径的核心生成逻辑)。

二、关键语法:虚函数(virtual)与方法重写(override

这是实现 "父类 Execute 调用子类 Process" 的核心语法,先看父类和子类的关键代码设计:

1. 父类 PathGeneration 的 "模板方法" 设计

PathGeneration 作为中间基类,用 模板方法模式 定义了 "固定流程 + 可变步骤":

  • 固定流程Execute 方法(非虚函数,不可被子类重写),负责流程的 "框架"(如前置检查、日志记录、调用 Process);
  • 可变步骤Process 方法(声明为 virtual 虚函数,允许子类重写),负责流程的 "核心逻辑"(如换道路径生成、直线路径生成)。

父类核心代码回顾:

cpp

运行

复制代码
class PathGeneration : public Task {
 public:
  // 1. 重写父类 Task 的 Execute 方法(固定流程,非虚函数)
  apollo::common::Status Execute(Frame* frame, ReferenceLineInfo* reference_line_info) override {
    Task::Execute(frame, reference_line_info);  // 调用顶层父类 Task 的 Execute(做基础初始化)
    return Process(frame, reference_line_info);  // 调用虚函数 Process(核心逻辑交给子类)
  }

 protected:
  // 2. 声明虚函数 Process(可变步骤,子类必须重写自己的逻辑)
  virtual apollo::common::Status Process(Frame* frame, ReferenceLineInfo* reference_line_info) {
    return apollo::common::Status::OK();  // 父类默认实现(空逻辑,子类需覆盖)
  }
};
  • 关键语法点 1:Processvirtual 修饰 → 声明为虚函数,允许子类重写,且调用时会根据 "对象的实际类型" 动态绑定到对应的实现(多态);
  • 关键语法点 2:Execute 没有 virtual → 是非虚函数,子类无法重写,强制复用父类的流程框架(保证所有子类的执行流程一致)。
2. 子类 LaneChangePath 的 "重写" 设计

LaneChangePath 作为具体的 "换道路径生成类",不重写 Execute (直接用父类的流程),只重写 Process(实现换道路径的核心逻辑),代码如下:

cpp

运行

复制代码
class LaneChangePath : public PathGeneration {
 private:
  // 重写父类 PathGeneration 的虚函数 Process(用 override 关键字确保重写正确)
  apollo::common::Status Process(Frame* frame, ReferenceLineInfo* reference_line_info) override {
    // 这里是 LaneChangePath 自己的核心逻辑:
    // 1. 计算换道的起始点和目标点(基于当前车道和目标车道);
    // 2. 生成换道的平滑路径(如基于多项式插值,满足车辆最小转弯半径);
    // 3. 校验路径是否避障(与障碍物的距离是否满足安全阈值);
    // 4. 将生成的路径存入 reference_line_info;
    return apollo::common::Status::OK();  // 返回自己的执行结果
  }
};
  • 关键语法点:override 关键字 → 显式声明 "该方法是重写父类的虚函数",编译器会检查父类是否存在对应的虚函数,避免因函数签名不一致导致 "重写失败"(如参数类型错误、返回值不匹配)。

三、调用流程:为什么父类 Execute 会返回子类 Process 的结果?

LaneChangePath 的对象调用 Execute 时,整个流程会通过 "多态" 自动绑定到子类的 Process,具体步骤如下:

步骤 1:调用 LaneChangePath::Execute

由于 LaneChangePath 没有重写 Execute,会直接调用父类 PathGenerationExecute 方法:

cpp

运行

复制代码
// 假设创建了 LaneChangePath 的对象
std::unique_ptr<PathGeneration> path_task = std::make_unique<LaneChangePath>();
// 调用 Execute,实际执行的是父类 PathGeneration 的 Execute
path_task->Execute(frame, reference_line_info);
步骤 2:父类 Execute 内部调用 Process

进入父类 PathGeneration::Execute 后,代码会执行:

cpp

运行

复制代码
return Process(frame, reference_line_info);  // 关键:调用虚函数 Process

此时,由于 Process 是虚函数,且 path_task实际类型是 LaneChangePath (虽然指针类型是父类 PathGeneration),C++ 会通过 "动态绑定" 找到 LaneChangePath 重写的 Process 方法,而非父类的默认实现。

步骤 3:返回子类 Process 的结果

LaneChangePath::Process 执行完换道路径生成逻辑后,返回自己的 Status(成功 / 失败),这个结果会被父类 Execute 直接返回给调用者。

最终效果:调用的是父类的 Execute 流程,但实际执行的是子类的 Process 逻辑,返回的也是子类 Process 的结果

四、这种设计的好处(为什么要这么写?)

Apollo 用这种 "父类框架 + 子类细节" 的设计,核心是为了复用流程、统一接口、灵活扩展

  1. 复用固定流程 :所有路径生成类(如 LaneChangePathStraightPathAvoidObstaclePath)的 Execute 流程一致(如前置初始化、日志记录),无需每个子类重复写一遍;
  2. 统一接口 :所有路径生成类都通过 PathGeneration 基类指针调用,调用者无需关心具体是哪个子类(如换道路径还是直线路径),符合 "开闭原则";
  3. 灵活扩展 :新增路径生成逻辑时(如 "紧急避障路径"),只需新建子类继承 PathGeneration,重写 Process 即可,无需修改父类代码。

总结:核心语法与设计模式

核心语法 / 模式 作用
虚函数(virtual 允许子类重写父类方法,实现 "动态绑定"(调用时根据对象实际类型找对应实现);
方法重写(override 显式声明重写,避免语法错误,增强代码可读性;
模板方法模式 父类定义固定流程(Execute),子类定制核心逻辑(Process),实现 "流程复用 + 细节灵活";

简单说:父类 PathGeneration 搭好了 "架子"(Execute),子类 LaneChangePath 只需要填自己的 "核心内容"(Process),就能通过父类的架子完成整个流程,同时返回自己的结果。

相关推荐
夫唯不争,故无尤也3 小时前
Maven创建Java项目实战全流程
java·数据仓库·hive·hadoop·maven
weixin_404551243 小时前
openrewrite Maven plugin configuration
java·maven·configuration·openrewrite
我是华为OD~HR~栗栗呀3 小时前
华为OD-23届考研-Java面经
java·c++·后端·python·华为od·华为·面试
Le1Yu3 小时前
服务注册、服务发现、OpenFeign及其OKHttp连接池实现
java·服务器
想ai抽3 小时前
深入starrocks-怎样实现多列联合统计信息
java·数据库·数据仓库
shepherd1113 小时前
⏰ 一招鲜吃遍天!详解Java延时队列DelayQueue,从此延时任务不再难!
java·后端·消息队列
小此方3 小时前
C语言自定义变量类型结构体理论:从初见到精通(下)
c语言·数据结构·算法
Small___ming3 小时前
【Python基础】Python路径操作全解析:os.path、glob与pathlib从入门到精通
开发语言·python
星光一影3 小时前
Java版小区物业管理系统/业主端/物业端/管理端/支持公众号、小程序、app
java·大数据·小程序