You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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;
}

这个双分派的工作流程是两次动态绑定的叠加:

  1. 第一次分派:调用collide(shape1, shape2)时,shape1->collide(shape2)会根据shape1的实际类型(比如Rectangle或Ellipse)动态调用对应的collide函数,这是第一次单分派。
  2. 第二次分派:在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*)),可以按以下步骤实现:

  1. 在基类Shape中新增三重分派相关的虚函数:除了原有双分派的函数,还要添加针对第三个参数的分派入口,以及对应所有类型组合的虚函数。
  2. 在派生类中重写这些虚函数:每一层重写都负责解析一个参数的实际类型,直到三个参数的类型都被确定,最终调用具体的三重碰撞逻辑。

这里给一个简化的代码片段示例:

// 基类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;
}

这个三重分派的流程是三次动态绑定的叠加:

  1. 第一次分派:解析第一个参数的实际类型;
  2. 第二次分派:解析第二个参数的实际类型;
  3. 第三次分派:解析第三个参数的实际类型;
    最终三个参数的类型都确定后,调用对应的具体逻辑。

注意事项

这种实现方式的缺点也很明显:如果有n个不同的派生类,三重分派需要实现n³个具体的逻辑函数,代码量会随着类型数量呈指数级增长。如果你的类型数量较多,可以考虑用std::type_index做类型映射,或者用模板元编程来自动生成这些分派函数,但核心思路还是基于动态绑定的多次单分派叠加。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 08:32:58