PAYATHON 2026

C++ 如何根据两个 Shape 的实际类型分派相交函数?

数据阿明

结论

可以使用双重分派(double dispatch),让两个对象各通过一次虚函数参与类型选择,从而避免手动逐个尝试 dynamic_cast。

原例中,s1 和 s2 的静态类型都是 Shape*。调用 s1->intersects(*s2) 时,虚函数会根据 s1 的实际类型选择实现,但参数 *s2 的静态类型仍是 Shape&,编译器无法据此选择接受 Circle 或 Line 的重载。全局函数调用也一样:intersect(*s1, *s2) 的两个实参在编译时都被视为 Shape&。

用双重分派选择两个实际类型

先声明四个具体类型对应的全局重载,再在 Shape 接口中声明用于分派第二个对象类型的虚函数:

class Circle;
class Line;

bool intersect(const Circle& first, const Circle& second);
bool intersect(const Circle& first, const Line& second);
bool intersect(const Line& first, const Circle& second);
bool intersect(const Line& first, const Line& second);

class Shape {
public:
    virtual ~Shape() = default;

    virtual bool intersects(const Shape& other) const = 0;

    // 这些虚函数用于分派第一个对象的具体类型。
    virtual bool intersectsWith(const Circle& first) const = 0;
    virtual bool intersectsWith(const Line& first) const = 0;
};

class Circle : public Shape {
public:
    bool intersects(const Shape& other) const override {
        return other.intersectsWith(*this);
    }

    bool intersectsWith(const Circle& first) const override {
        return intersect(first, *this);
    }

    bool intersectsWith(const Line& first) const override {
        return intersect(first, *this);
    }
};

class Line : public Shape {
public:
    bool intersects(const Shape& other) const override {
        return other.intersectsWith(*this);
    }

    bool intersectsWith(const Circle& first) const override {
        return intersect(first, *this);
    }

    bool intersectsWith(const Line& first) const override {
        return intersect(first, *this);
    }
};

bool intersect(const Shape* first, const Shape* second) {
    // 调用者须保证两个指针都非空;也可以在这里定义空指针的处理方式。
    return first->intersects(*second);
}

四个全局重载分别负责对应的几何计算:

bool intersect(const Circle& first, const Circle& second) {
    // Circle 与 Circle 的相交计算
}

bool intersect(const Circle& first, const Line& second) {
    // Circle 与 Line 的相交计算
}

bool intersect(const Line& first, const Circle& second) {
    // Line 与 Circle 的相交计算
}

bool intersect(const Line& first, const Line& second) {
    // Line 与 Line 的相交计算
}

函数体需要填入实际的几何逻辑。如果这里只展示分派结构,可以先声明这些重载,再在其他位置定义。

分派过程

调用 intersect(first, second) 后,分派分两步进行:

  1. first->intersects(*second) 是虚调用,会根据 first 的实际类型进入 Circle::intersects 或 Line::intersects。
  2. 该实现接着调用 second.intersectsWith(*this)。这也是虚调用,会根据 second 的实际类型选择实现。此时,*this 在对应类中的静态类型已知,因此会匹配正确的重载。最终调用的全局函数就对应这两个对象的具体类型。

例如,若 first 指向 Circle,second 指向 Line,最终调用的是 intersect(const Circle&, const Line&)。

需要留意

Shape 需要声明虚析构函数,才能通过基类指针安全地销毁派生类对象。示例中的指针也必须保证非空,或者由接口明确处理空指针。

新增形状时,需要扩展 Shape 的分派接口,并为现有形状补上相应实现。如果类型集合固定,也可以考虑使用 std::variant 配合 std::visit。对于通过基类接口管理的开放式继承层次,双重分派是常见做法。

备注:内容仅供参考。