当前位置: 首页 > news >正文

双重调度(Double Dispatch):《More Effective C++》条款31

《More Effective C++》中的条款31(“让函数根据一个以上的对象类型来决定如何虚化”)聚焦于解决C++中 双重调度(Double Dispatch) 的挑战。C++的虚函数机制(单一调度)仅能根据一个对象的动态类型决定行为,但实际场景中(如碰撞检测、类型交互)常需同时依赖多个对象的类型。本条款通过具体案例和设计模式,提出了在C++中实现多对象类型驱动行为的可行方案。

一、问题背景:为什么单一调度不够?

条款以游戏开发中的碰撞处理为例:

  • 飞船与空间站:低速碰撞时停靠,高速时双方受损。
  • 小行星与飞船:小行星毁灭,若体积较大则飞船也损坏。
  • 飞船与飞船:双方受损程度与速度成正比。

传统虚函数只能根据一个对象的动态类型调度,无法同时处理两个对象的类型组合。例如,若调用object1.collide(object2),虚函数仅能根据object1的类型选择实现,而object2的类型仍需通过RTTI或分支逻辑判断,导致代码扩展性差且易出错。

二、解决方案:双重调度的实现

条款提出了三种核心方法,均通过两次虚函数调用类型驱动的逻辑组合实现双重调度。

1. 虚函数重载与递归调用

通过在基类中声明针对所有可能派生类的虚函数,利用递归调用触发两次动态绑定:

class GameObject {
public:virtual void collide(GameObject& other) = 0;virtual void collide(SpaceShip& other) = 0;virtual void collide(SpaceStation& other) = 0;virtual void collide(Asteroid& other) = 0;
};class SpaceShip : public GameObject {
public:void collide(GameObject& other) override {other.collide(*this); // 第一次调度:根据other的动态类型}void collide(SpaceShip& other) override { /* 处理飞船-飞船碰撞 */ }void collide(SpaceStation& other) override { /* 处理飞船-空间站碰撞 */ }// 其他类型的处理...
};
  • 核心逻辑:当SpaceShip调用collide(other)时,other的动态类型决定第一次调度;随后other调用collide(*this)*this的静态类型(SpaceShip)触发第二次调度,最终调用对应的重载函数。
  • 优点:完全依赖虚函数机制,类型安全且避免RTTI。
  • 缺点:新增派生类需修改所有相关类的虚函数声明,违反开闭原则。
2. 访问者模式(Visitor Pattern)

将操作与数据结构分离,通过访问者类实现双重调度:

class Visitor {
public:virtual void visit(SpaceShip&) = 0;virtual void visit(SpaceStation&) = 0;virtual void visit(Asteroid&) = 0;
};class GameObject {
public:virtual void accept(Visitor& visitor) = 0;
};class SpaceShip : public GameObject {
public:void accept(Visitor& visitor) override {visitor.visit(*this); // 触发双重调度}
};class CollisionVisitor : public Visitor {
public:void visit(SpaceShip& ship) override { /* 处理飞船相关碰撞 */ }void visit(SpaceStation& station) override { /* 处理空间站相关碰撞 */ }
};
  • 核心逻辑:每个GameObject接受一个Visitor,调用其visit方法时,Visitor的具体类型和GameObject的动态类型共同决定行为。
  • 优点:新增操作(如计算碰撞力、记录日志)只需添加新的Visitor,无需修改现有类。
  • 缺点:新增GameObject类型需修改所有Visitor接口,扩展性受限。
3. 函数映射表与静态类型键

通过静态类型标识(如类名)构建映射表,动态查找处理函数:

using CollisionFunc = void (*)(GameObject&, GameObject&);
std::map<std::pair<std::string, std::string>, CollisionFunc> collisionMap;void registerCollision(GameObjectType type1, GameObjectType type2, CollisionFunc func) {collisionMap[{type1.name(), type2.name()}] = func;
}void processCollision(GameObject& a, GameObject& b) {auto it = collisionMap.find({typeid(a).name(), typeid(b).name()});if (it != collisionMap.end()) {it->second(a, b);} else {throw UnknownCollisionException();}
}
  • 核心逻辑:在初始化阶段注册所有可能的类型组合对应的处理函数,运行时通过类型名称查找。
  • 优点:无需修改类层次结构,适合动态扩展。
  • 缺点:依赖typeid的字符串表示(非标准行为),继承体系中的类型转换可能导致匹配失败。

三、条款中的关键权衡与注意事项

  1. 二进制兼容性:虚函数重载方案中,新增派生类需重新编译所有相关类,可能破坏已有二进制接口。
  2. 类型安全:函数映射表方案若未正确注册所有类型组合,可能导致运行时错误,需配合异常处理机制。
  3. 设计选择
    • 频繁新增操作:优先选择访问者模式。
    • 频繁新增类型:优先选择虚函数重载或函数映射表。
    • 动态扩展需求:函数映射表更灵活,但需牺牲部分类型安全。

四、总结

条款31揭示了C++单一调度的局限性,并通过虚函数重载、访问者模式和函数映射表三种方案实现双重调度。其核心思想是通过递归调用、类型分离或静态映射,将多个对象的类型信息结合起来,最终实现动态行为的精准控制。开发者需根据具体场景(如类型稳定性、操作扩展性)选择合适方案,同时注意二进制兼容性和类型安全的权衡。

http://www.dtcms.com/a/334233.html

相关文章:

  • RD-Agent for Quantitative Finance (RD-Agent(Q))
  • C#单元测试(xUnit + Moq + coverlet.collector)
  • 深度学习——常见问题与优化改进
  • java中消息推送功能
  • Xiaothink-T6-0.15B混合架构模型深度解析
  • 3 种方式玩转网络继电器!W55MH32 实现网页 + 阿里云 + 本地控制互通
  • 架构调整决策
  • 超越Transformer:大模型架构创新的深度探索
  • 【计算机网络架构】混合型架构简介
  • Blackwell 和 Hopper 架构的 GPGPU 新功能全面综述
  • 【LeetCode每日一题】
  • Mac (三)如何设置环境变量
  • 从希格斯玻色子到 QPU:C++ 的跨维度征服
  • 代码随想录Day52:图论(孤岛的总面积、沉没孤岛、水流问题、建造最大岛屿)
  • 在ubuntu系统上离线安装jenkins的做法
  • 立体匹配中的稠密匹配和稀疏匹配
  • 8.16 pq
  • [系统架构设计师]系统质量属性与架构评估(八)
  • 解锁JavaScript性能优化:从理论到实战
  • 【完整源码+数据集+部署教程】太阳能面板污垢检测系统源码和数据集:改进yolo11-RVB-EMA
  • 地级市+省级气候政策不确定性指数(2000-2023年)-实证数据
  • ollama 自定义模型
  • imx6ull-驱动开发篇27——Linux阻塞和非阻塞 IO(上)
  • 【JS】认识并实现一个chrome扩展程序
  • 如何在 MacOS 上安装 SQL Server
  • MySQL完整重置密码流程(针对 macOS)
  • 硬核北京 | 2025世界机器人大会“破圈”,工业智能、康养科技…… 亦庄上演“机器人总动员”
  • Flink Sql 按分钟或日期统计数据量
  • 中本聪思想与Web3的困境:从理论到现实的跨越
  • 存算分离与云原生:数据平台的新基石