方法参数中的动态类型匹配

Dynamic type matching in method argument

我有基础 class Shape 和一些继承自它的子 classes,如 Circle、Rectangle、AlignedRectangle、ConvexPolygon、ConcavePolygon 等。我希望每个这样的 classes有方法 bool intersect(Shape &other); 检查两个形状是否相交。我想对不同的 classes 对使用这种方法的不同实现,因为某些交点的计算速度比蛮力方法快得多。 我想要这样的方法,这样我就可以将两个 Shape* 指针相交,而不必关心内部实际是什么类型。

这是我目前的做法。

class Circle;
class Rect;

class Shape {
 public:
    Shape() {}

    virtual bool intersect(Shape &a) = 0;
    virtual bool intersect_circle(Circle &a) = 0;
    virtual bool intersect_rect(Rect &a) = 0;
};

class Circle : public Shape {
 public:
    Circle() {}
    virtual bool intersect(Shape &a);
    virtual bool intersect_circle(Circle &a);
    virtual bool intersect_rect(Rect &a);
};

class Rect : public Shape {
 public:
    Rect() {}
    virtual bool intersect(Shape &a);
    virtual bool intersect_circle(Circle &a);
    virtual bool intersect_rect(Rect &a);
};

bool Circle::intersect(Shape &a) {
    a.intersect_circle(*this);
}

bool Circle::intersect_circle(Circle &a) {
    cout << "Circle::intersect_circle" << endl;
}

bool Circle::intersect_rect(Rect &a) {
    cout << "Circle::intersect_rect" << endl;
}

bool Rect::intersect(Shape &a) {
    a.intersect_rect(*this);
}

bool Rect::intersect_circle(Circle &a) {
    cout << "Rect::intersect_circle" << endl;
}

bool Rect::intersect_rect(Rect &a) {
    cout << "Rect::intersect_rect" << endl;
}

还有其他 'better' 方法吗? 两个形状的交集可能没问题,但是如果我想要有两个或多个可以具有任何类型的参数的方法怎么办?这是 3 classes 案例的扩展:

void Circle::some_stuff(Shape &a, Shape &b) {
    a.some_stuff_circle(*this, b);
}

void OtherClass::some_stuff_circle(Circle &a, Shape &b) {
    b.some_stuff_circle_other_class(a, *this);
}

void AnotherClass::some_stuff_circle_other_class(Circle &a, OtherClass &b) {
    //do things here
}

会有很多冗余代码。不过我可能遗漏了一些基本的东西。

您可以为此使用变体类型。这是使用我的 Polyvar header 的一种可能的解决方案。它不是最漂亮也不是最灵活的解决方案,但它确实允许您有选择地重载 intersect 并且仍然具有动态多态行为。我认为它和你在 C++ 中获得的一样好。

注:Polyvar 依赖于Boost.Variant,如果你使用它as-is。如果您想使用 std::variant 或其他一些变体实现,请随意调整宏。

#include "polyvar.hpp"
#include <iostream>

// Define a variant template with a self-visiting member
// function named `intersect`. We'll use this to emulate a base class
DEFINE_POLYVAR(ShapeVariant, (intersect));

class Circle {

 public:

    Circle() {}

    template<typename T>
    bool intersect(T &a) {
        std::cout << "Circle to ???\n";
        auto your_implementation = false;
        return your_implementation;
    }
    bool intersect(Circle &a) {
        std::cout << "Circle to Circle\n";
        auto your_implementation = false;
        return your_implementation;
    }

    bool intersect(class Rect &a) {
        std::cout << "Circle to Rect\n";
        auto your_implementation = false;
        return your_implementation;
    }
};

class Rect {

 public:

    Rect(){}

    template<typename T>
    bool intersect(T &a) {
        std::cout << "Rect to ???\n";
        auto your_implementation = false;
        return your_implementation;
    }
    bool intersect(Circle &a) {
        std::cout << "Rect to Circle\n";
        auto your_implementation = false;
        return your_implementation;
    }

    bool intersect(Rect &a) {
        std::cout << "Rect to Rect\n";
        auto your_implementation = false;
        return your_implementation;
    }
};

class Triangle {

 public:

    Triangle(){}

    template<typename T>
    bool intersect(T &a) {
        std::cout << "Triangle to ???\n";
        auto your_implementation = false;
        return your_implementation;
    }

    bool intersect(Triangle &a) {
        std::cout << "Triangle to Triangle\n";
        auto your_implementation = false;
        return your_implementation;
    }
};

using Shape = ShapeVariant<Circle, Rect, Triangle /*, etc */>;

// Polyvar adds one level of visitation, but we must add another.
bool intersect(Shape& s1, Shape& s2) {

    auto visitor = [&s1](auto& s2) {
        return s1.intersect(s2);
    };

    return boost::apply_visitor(visitor, s2);
}

int main () {

    Shape s1 = Circle{};
    Shape s2 = Rect{};
    Shape s3 = Triangle{};

    intersect(s1, s2);
    intersect(s2, s1);
    intersect(s1, s3);
    intersect(s3, s2);
    intersect(s1, s1);
    intersect(s2, s2);
    intersect(s3, s3);
}

输出:

圆转正

直角转圆

圈到 ???

三角形到 ???

圆到圆

矩形到矩形

三角形到三角形


Live example


另请参阅 - 多重分派:


顺便说一句,别忘了使您的代码 const-正确。

为了避免重复,我想到了这个:

#include <iostream>
#include <functional>
using namespace std;

class Circle;
class Rect;

class Shape {
public:
   enum Type
   {
      CircleType, RectType, UnknownType
   };

   Shape(Type t)
      : type_m(t)
   {}

   virtual ~Shape() {} // don't forget to declare it virtual!!
   bool intersect(Shape &a);
private:
   Type type_m;
};

bool Circle2Circle(const Shape& circle1, const Shape& circle2);
bool Circle2Rect(const Shape& circle, const Shape& rect);
bool Rect2Rect(const Shape& rect1, const Shape& rect2);

std::function<bool(const Shape&, const Shape&)> intersectFuncTable[Shape::UnknownType][Shape::UnknownType] =
{ 
   { Circle2Circle, Circle2Rect },
   { Circle2Rect, Rect2Rect }
};

bool Shape::intersect(Shape &a)
{
   return intersectFuncTable[type_m][a.type_m](*this, a);
}

class Circle : public Shape {
public:
   Circle() 
      : Shape(CircleType)
   {}
};

bool Circle2Circle(const Shape& circle1, const Shape& circle2)
{
   if (dynamic_cast<const Circle*>(&circle1) == nullptr ||
      (dynamic_cast<const Circle*>(&circle2)) == nullptr)
      return false;
   cout << "circle to circle\n";
  return true;
};

class Rect : public Shape {
public:
   Rect() 
      : Shape(RectType)  
   {}
};

bool Circle2Rect(const Shape& shape1, const Shape& shape2)
{
   if (dynamic_cast<const Circle*>(&shape1) != nullptr && dynamic_cast<const Rect*>(&shape2) != nullptr ||
      dynamic_cast<const Rect *>(&shape1) != nullptr && dynamic_cast<const Circle*>(&shape2) != nullptr)
   {
      cout << "circle to rect\n";
      return true;
   }
   return false;
};

bool Rect2Rect(const Shape& rect1, const Shape& rect2)
{
   if (dynamic_cast<const Rect*>(&rect1) == nullptr|| dynamic_cast<const Rect*>(&rect2) == nullptr)
      return false;
   cout << "rect to rect\n";
   return true;
}

int main()
{
   Circle cir;
   Rect rect;

   cir.intersect(rect);
   rect.intersect(cir);
   cir.intersect(cir);
   rect.intersect(rect);

   return 0;
}

想法是实现每个相交函数,并且在添加新类型时向基础 class 中的枚举添加一个类型,并添加所需的相交函数。 另外 - 再想一想 - 最好使用 type_m 而不是动态转换,因为访问数据成员的成本更低。