【问题标题】:Dynamic type matching in method argument方法参数中的动态类型匹配
【发布时间】:2017-01-14 11:55:56
【问题描述】:

我有基类 Shape 和一些继承自它的子类,如 Circle、Rectangle、AlignedRectangle、ConvexPolygon、ConcavePolygon 等。我希望每个这样的类都有方法 bool intersect(Shape &other); 检查两个形状是否相交。我想为不同的类对使用这种方法的不同实现,因为某些交集的计算速度比蛮力方法要快得多。 我想要这样的方法,这样我就可以与两个 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;
}

还有其他“更好”的方法吗? 两个形状的交集可能很好,但是如果我想要一个方法有两个或多个可以有任何类型的参数怎么办?这是 3 类案例的扩展:

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
}

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

【问题讨论】:

  • 您已经彻底改造了访问者模式。顺便说一句,多参数动态调度是 Bjarne 所写的,因此它可能会在未来出现在标准 C++ 中。您可能想搜索“C++ 中的多方法”

标签: c++ oop derived-class visitor-pattern


【解决方案1】:

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

#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;
}

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

【讨论】:

    【解决方案2】:

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

    注意:Polyvar 依赖于 Boost.Variant,如果你按原样使用它的话。如果您想使用 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-正确。

    【讨论】:

      猜你喜欢
      • 2021-01-15
      • 1970-01-01
      • 2016-01-29
      • 1970-01-01
      • 1970-01-01
      • 2023-03-06
      • 2021-09-13
      • 1970-01-01
      • 2020-03-09
      相关资源
      最近更新 更多