如何在C++中实现Triple Dispatch?
如何在C++中实现Triple Dispatch?
嘿,我懂你现在的困惑——C++本身并没有原生支持多分派(比如三重分派),但咱们可以通过嵌套的虚函数调用(把多次单分派的动态绑定组合起来)来实现它,就像你已经看到的双分派那样。先从你提供的形状碰撞双分派例子入手,再一步步扩展到三重分派的场景。
先回顾双分派的实现原理
你给的形状碰撞代码已经完美展示了双分派的思路,先把代码整理好方便理解:
#include <iostream> #include <string> #include <string_view> struct Rectangle; struct Ellipse; struct Shape { Shape(std::string_view name) : name(name) { } virtual ~Shape() { } std::string get_name() const { return name; } virtual void collide(Shape* shape2) = 0; virtual void collideRectangle(Rectangle* rectangle1) = 0; virtual void collideEllipse(Ellipse* ellipse1) = 0; std::string name; }; // 具体的碰撞逻辑声明 void collideRectangleRectangle(Rectangle* rectangle1, Rectangle* rectangle2); void collideRectangleEllipse(Rectangle* rectangle1, Ellipse* ellipse2); void collideEllipseRectangle(Ellipse* ellipse1, Rectangle* rectangle2); void collideEllipseEllipse(Ellipse* ellipse1, Ellipse* ellipse2); struct Rectangle : public Shape { Rectangle(std::string_view name) : Shape(name) { } void collide(Shape* shape2) override { std::cout << "collide(Shape*, Shape*) resolved to Rectangle::collide(Shape*)" << std::endl; shape2->collideRectangle(this); } void collideRectangle(Rectangle* rectangle1) override { std::cout << "resolved to Rectangle::collideRectangle(Rectangle*)" << std::endl; collideRectangleRectangle(rectangle1, this); } void collideEllipse(Ellipse* ellipse1) override { std::cout << "resolved to Rectangle::collideEllipse(Ellipse*)" << std::endl; collideEllipseRectangle(ellipse1, this); } }; struct Ellipse : public Shape { Ellipse(std::string_view name) : Shape(name) { } void collide(Shape* shape2) override { std::cout << "collide(Shape*, Shape*) resolved to Ellipse::collide(Shape*)" << std::endl; shape2->collideEllipse(this); } void collideRectangle(Rectangle* rectangle1) override { std::cout << "resolved to Ellipse::collideRectangle(Rectangle*)" << std::endl; collideRectangleEllipse(rectangle1, this); } void collideEllipse(Ellipse* ellipse1) override { std::cout << "resolved to Ellipse::collideEllipse(Ellipse*)" << std::endl; collideEllipseEllipse(ellipse1, this); } }; // 具体碰撞逻辑实现 void collideRectangleRectangle(Rectangle* rectangle1, Rectangle* rectangle2) { std::cout << rectangle1->get_name() << " collides with " << rectangle2->get_name() << std::endl; } void collideRectangleEllipse(Rectangle* rectangle1, Ellipse* ellipse2) { std::cout << rectangle1->get_name() << " collides with " << ellipse2->get_name() << std::endl; } void collideEllipseRectangle(Ellipse* ellipse1, Rectangle* rectangle2) { std::cout << ellipse1->get_name() << " collides with " << rectangle2->get_name() << std::endl; } void collideEllipseEllipse(Ellipse* ellipse1, Ellipse* ellipse2) { std::cout << ellipse1->get_name() << " collides with " << ellipse2->get_name() << std::endl; } void collide(Shape* shape1, Shape* shape2) { shape1->collide(shape2); } int main() { Shape* rectangle1 = new Rectangle("Rectangle1"); Shape* rectangle2 = new Rectangle("Rectangle2"); Shape* ellipse1 = new Ellipse("Ellipse1"); Shape* ellipse2 = new Ellipse("Ellipse2"); std::cout << "collide(Rectangle*, Rectangle*)" << std::endl; collide(rectangle1, rectangle2); std::cout << std::endl; std::cout << "collide(Rectangle*, Ellipse*)" << std::endl; collide(rectangle1, ellipse2); std::cout << std::endl; std::cout << "collide(Ellipse*, Rectangle*)" << std::endl; collide(ellipse1, rectangle2); std::cout << std::endl; std::cout << "collide(Ellipse*, Ellipse*)" << std::endl; collide(ellipse1, ellipse2); std::cout << std::endl; delete rectangle1; delete rectangle2; delete ellipse1; delete ellipse2; return 0; }
这个双分派的工作流程是两次动态绑定的叠加:
- 第一次分派:调用
collide(shape1, shape2)时,shape1->collide(shape2)会根据shape1的实际类型(比如Rectangle或Ellipse)动态调用对应的collide函数,这是第一次单分派。 - 第二次分派:在
Rectangle::collide(Shape*)里,会调用shape2->collideRectangle(this),此时又会根据shape2的实际类型,动态调用它的collideRectangle或collideEllipse方法,完成第二次分派,最终匹配到具体的碰撞逻辑。
它的完整输出应该是这样的:
collide(Rectangle*, Rectangle*) collide(Shape*, Shape*) resolved to Rectangle::collide(Shape*) resolved to Rectangle::collideRectangle(Rectangle*) Rectangle1 collides with Rectangle2 collide(Rectangle*, Ellipse*) collide(Shape*, Shape*) resolved to Rectangle::collide(Shape*) resolved to Ellipse::collideRectangle(Rectangle*) Rectangle1 collides with Ellipse2 collide(Ellipse*, Rectangle*) collide(Shape*, Shape*) resolved to Ellipse::collide(Shape*) resolved to Rectangle::collideEllipse(Ellipse*) Ellipse1 collides with Rectangle2 collide(Ellipse*, Ellipse*) collide(Shape*, Shape*) resolved to Ellipse::collide(Shape*) resolved to Ellipse::collideEllipse(Ellipse*) Ellipse1 collides with Ellipse2
扩展到三重分派的思路
三重分派的核心逻辑和双分派完全一致:在双分派的基础上,再增加一层虚函数调用,把第三个参数的实际类型通过动态绑定解析出来。
假设我们现在需要处理三个形状的碰撞(比如collide(Shape*, Shape*, Shape*)),可以按以下步骤实现:
- 在基类
Shape中新增三重分派相关的虚函数:除了原有双分派的函数,还要添加针对第三个参数的分派入口,以及对应所有类型组合的虚函数。 - 在派生类中重写这些虚函数:每一层重写都负责解析一个参数的实际类型,直到三个参数的类型都被确定,最终调用具体的三重碰撞逻辑。
这里给一个简化的代码片段示例:
// 基类Shape新增三重分派相关虚函数 struct Shape { // ... 原有双分派的虚函数和成员 ... // 三重分派的入口函数 virtual void collideThree(Shape* s2, Shape* s3) = 0; // 针对第二个参数是Rectangle的情况,处理第三个参数的分派 virtual void collideThreeRectangle(Rectangle* s2, Shape* s3) = 0; // 针对第二个参数是Ellipse的情况,处理第三个参数的分派 virtual void collideThreeEllipse(Ellipse* s2, Shape* s3) = 0; // 三个参数都是具体类型的虚函数(覆盖所有组合) virtual void collideThreeRectRectRect(Rectangle* s2, Rectangle* s3) = 0; virtual void collideThreeRectRectEllipse(Rectangle* s2, Ellipse* s3) = 0; virtual void collideThreeRectEllipseRect(Rectangle* s2, Rectangle* s3) = 0; // ... 其他类型组合的虚函数 ... }; // Rectangle类中三重分派的实现 struct Rectangle : public Shape { // ... 原有双分派的函数 ... void collideThree(Shape* s2, Shape* s3) override { // 第一次分派:根据s2的实际类型,调用对应处理函数 s2->collideThreeRectangle(this, s3); } void collideThreeRectangle(Rectangle* s1, Shape* s3) override { // 第二次分派:根据s3的实际类型,调用对应具体类型的函数 s3->collideThreeRectRectRect(s1, this); } void collideThreeRectRectRect(Rectangle* s1, Rectangle* s2) override { // 第三次分派完成,调用具体的三重碰撞逻辑 collideThreeRectangleRectangleRectangle(s1, s2, this); } // ... 其他类型组合的重写函数 ... }; // 具体的三重碰撞逻辑实现 void collideThreeRectangleRectangleRectangle(Rectangle* s1, Rectangle* s2, Rectangle* s3) { std::cout << "三个矩形碰撞:" << s1->name << ", " << s2->name << ", " << s3->name << std::endl; }
这个三重分派的流程是三次动态绑定的叠加:
- 第一次分派:解析第一个参数的实际类型;
- 第二次分派:解析第二个参数的实际类型;
- 第三次分派:解析第三个参数的实际类型;
最终三个参数的类型都确定后,调用对应的具体逻辑。
注意事项
这种实现方式的缺点也很明显:如果有n个不同的派生类,三重分派需要实现n³个具体的逻辑函数,代码量会随着类型数量呈指数级增长。如果你的类型数量较多,可以考虑用std::type_index做类型映射,或者用模板元编程来自动生成这些分派函数,但核心思路还是基于动态绑定的多次单分派叠加。
内容来源于stack exchange
相关产品推荐
相关产品推荐

