解决此多态性问题的最优雅方法 编辑:我正在使用C++。< /P>

解决此多态性问题的最优雅方法 编辑:我正在使用C++。< /P>,c++,polymorphism,C++,Polymorphism,因此,我正在创建方法/函数来测试形状之间的相交。我基本上有: class Shape {}; class Rectangle : public Shape {}; class Circle : public Shape {}; class Line : public Shape {}; 现在,我需要决定编写测试交叉点的实际方法/函数的最佳方式。但是我的所有形状都将存储在形状指针列表中,因此我将调用基本形式的方法/函数: bool intersects (Shape* a, Shape*

因此,我正在创建方法/函数来测试形状之间的相交。我基本上有:

class Shape {};

class Rectangle : public Shape {};

class Circle : public Shape {};

class Line : public Shape {};
现在,我需要决定编写测试交叉点的实际方法/函数的最佳方式。但是我的所有形状都将存储在形状指针列表中,因此我将调用基本形式的方法/函数:

bool intersects (Shape* a, Shape* b);
在这一点上,我需要确定“a”和“b”是什么类型的形状,这样我才能正确地检测碰撞。通过使用一些虚拟方法,我可以轻松完成其中一项:

class Shape
{
    virtual bool intersects (Shape* b) = 0;
}
这将决定其中一个形状(“a”现在是“this”)。然而,我仍然需要得到“b”的类型。显而易见的解决方案是给Shape一个“id”变量来分类它是哪个形状,然后在这些形状之间进行“切换”,然后使用dynamic_cast。然而,这并不是很优雅,而且感觉应该有一种更加面向对象的方法来做到这一点


有什么建议吗?

Andrei Alexandrescu在他的经典著作中详细描述了这个问题。伴奏库Loki包含

更新


根据用户的需要,Loki提供了三种多种方法的实现。有些是为了简单,有些是为了速度,有些是为了低耦合,有些比其他提供更多的安全。本书中的这一章共有近40页,假设读者熟悉本书的许多概念——如果你能自如地使用boost,那么Loki可能就是你的拿手好戏了。我真的不能把它归结为一个可以接受的答案,但是我已经指出了C++所涉及的主题的最好解释。

< p>您可以在每个代码< >代码> < /p> 例如:

class Shape {
  virtual shapetype_t getShapeType() const;
  // ...
}

正如@Mandarse所指出的,这是典型的双重分派问题。在面向对象语言中,或者像C++语言那样,可以实现面向对象的概念,这通常是用模式来解决的。 通常,
Visitor
接口本身为每个具体类型定义一个回调

class Circle;
class Rectangle;
class Square;

class Visitor {
public:
  virtual void visit(Circle const& c) = 0;
  virtual void visit(Rectangle const& r) = 0;
  virtual void visit(Square const& s) = 0;
};
然后,
形状
层次结构适用于此。我们需要两种方法:一种是接受任何类型的访问者,另一种是创建“适当的”交叉点访问者

class Visitor;
class Intersecter;

class Shape {
public:
  virtual void accept(Visitor&) const = 0; // generic
  virtual Intersecter* intersecter() const = 0;
};
相交点很简单:

#include "project/Visitor.hpp"

class Intersecter: public Visitor {
public:
  Intersecter(): result(false) {}
  bool result;
};
例如,对于圆,它将给出:

#include "project/Intersecter.hpp"
#include "project/Shape.hpp"

class Circle;

class CircleIntersecter: public Intersecter {
public:
  explicit CircleIntersecter(Circle const& c): _left(c) {}

  virtual void visit(Circle const& c);    // left is Circle, right is Circle
  virtual void visit(Rectangle const& r); // left is Circle, right is Rectangle
  virtual void visit(Square const& s);    // left is Circle, right is Square

private:
  Circle const& _left;
}; // class CircleIntersecter


class Circle: public Shape {
public:
  virtual void accept(Visitor& v) const { v.visit(*this); }

  virtual CircleIntersecter* intersecter() const {
    return new CircleIntersecter(*this);
  }
};
以及用法:

#include "project/Intersecter.hpp"
#include "project/Shape.hpp"

bool intersects(Shape const& left, Shape const& right) {
  boost::scope_ptr<Intersecter> intersecter(left.intersecter());
  right.accept(*intersecter);
  return intersecter->result;
};
#包括“project/intersector.hpp”
#包括“project/Shape.hpp”
布尔相交(形状常数与左、形状常数与右){
boost::scope_ptr intersecter(left.intersecter());
右。接受(*相交符);
返回相交符->结果;
};
如果其他方法需要双重分派机制,那么您所需要做的就是创建另一个“类交集”类,该类包装结果并从
Visitor
继承,以及一个新的扎根于
Shape
的“工厂”方法,该方法由派生类覆盖以提供适当的操作。这有点冗长,但确实有效


注:除了
相交(圆,矩形)
相交(矩形,圆)
以产生相同的结果是合理的。您可以将代码分解为一些方法,并让
CircleIntersecter::visit
委托给具体的实现。这可以避免代码重复。

C++运行时多态性只有一个分派(基类vtable)

您的问题有各种各样的解决方案,但没有一种是“优雅的”,因为它们都试图迫使语言做更多它本机可以支持的事情(Alexandrescu Loki multimethods是一组非常好的隐藏的黑客:它封装了“坏东西”,但并不能带来好东西)

这里的概念是,您需要编写所有可能组合的N2函数,并根据两个参数的实际运行时类型找到调用它们的方法。 “访问者模式”(从另一个虚拟函数回调一个虚拟函数)、“多方法”技术(使用一个通用的dspatch表)、“动态转换”到一个虚拟函数或“双重动态转换”所有函数都做相同的事情:在两次间接寻址后调用一个函数。从技术上讲,它们都不能被定义为“比另一个更好”,因为产生的性能基本相同

但其中一些在代码编写方面比另一个成本更高,而另一些在代码维护方面成本更高。
在你的案例中,你最有可能尝试去估计权衡是什么。您认为将来还需要添加多少其他类?

我使用shapes方法只是为了好玩。我不喜欢每次新形状出现时都要延长课程的想法。我想到了交集解析器的集合,它被迭代以确定是否有一个交集解析器支持给定的一对形状。如果出现新形状,将向集合中添加新的交点解析程序

class Intersection_resolvers {
    std::vector<IIntersection_resolver*> resolvers_;
    public:
    Intersection_resolvers(std::vector<IIntersection_resolver*> resolvers) :resolvers_{resolvers} {}
    Intersection_resolution intersect(Shape& s1, Shape& s2) {
        Intersection_resolution intersection_resolution;
        for (IIntersection_resolver* resolver : resolvers_) {
            intersection_resolution = resolver->intersect(s1, s2);
            if (intersection_resolution.supported)
                break;
        }
        return intersection_resolution;
    }
};

Intersection_resolver<Rectangle, Rectangle> rri;
Intersection_resolver<Rectangle, Circle> rci;

Intersection_resolvers intersection_resolvers{{&rri, &rci}};
我不认为它是性能方面最理想的方法,因为解析器会迭代并执行动态强制转换,直到找到合适的解析器

但是,尽管如此

交集解析程序采用两种形状并返回包含支持和交集标志的解析结果

struct Intersection_resolution {
    bool supported;
    bool intersect;
};

class IIntersection_resolver {
    public:
        virtual Intersection_resolution intersect(Shape& shape1, Shape& shape2) = 0;
};

解析器实现。模板类,采用两种形状,检查它是否支持它们,如果支持,则调用check_交叉方法。后者应在规范中定义。请注意,应该只指定一对,即如果指定了矩形-圆形,则无需指定圆形-矩形

template<typename S1, typename S2>
class Intersection_resolver : public IIntersection_resolver {
    private:
        bool check_intersection(S1& s1, S2& s2);
    public:
        Intersection_resolution intersect(Shape& shape1, Shape& shape2) override final {
            S1* s1 = dynamic_cast<S1*>(&shape1);
            S2* s2{nullptr};
            if (s1) 
                s2 = dynamic_cast<S2*>(&shape2);
            else {
                s1 = dynamic_cast<S1*>(&shape2);
                if (s1)
                    s2 = dynamic_cast<S2*>(&shape1);
            }
            bool supported{false};
            bool intersect{false};
            if (s1 && s2) {
                supported = true;
                intersect = check_intersection(*s1, *s2);
            }
            return Intersection_resolution{supported, intersect};
        }
};

这是通过双重分派解决的经典问题。虽然我最初的搜索从未出现双重分派,但我知道它是如何解决我的问题的。谢谢编辑:我会投赞成票,但我不确定我是否没有足够的声誉,或者我就是找不到“投赞成票”按钮。@anon\u downvoter除非你证明你的行为是正确的,否则这不会有多大帮助。也许是缺少代码?无论如何,是的,多种方法都会有帮助。@MatthieuM。也许。我扩展了我的答案来解释原因
class Intersection_resolvers {
    std::vector<IIntersection_resolver*> resolvers_;
    public:
    Intersection_resolvers(std::vector<IIntersection_resolver*> resolvers) :resolvers_{resolvers} {}
    Intersection_resolution intersect(Shape& s1, Shape& s2) {
        Intersection_resolution intersection_resolution;
        for (IIntersection_resolver* resolver : resolvers_) {
            intersection_resolution = resolver->intersect(s1, s2);
            if (intersection_resolution.supported)
                break;
        }
        return intersection_resolution;
    }
};

Intersection_resolver<Rectangle, Rectangle> rri;
Intersection_resolver<Rectangle, Circle> rci;

Intersection_resolvers intersection_resolvers{{&rri, &rci}};
int main() {
    Rectangle r;
    Triangle t;
    Circle c;
    Shape* shapes[]{&r, &t, &c};
    for (auto shape : shapes) {
        shape->draw();
    }
    for (auto shape : shapes) {
        for (auto other : shapes) {
            auto intersection_resolution = intersection_resolvers.intersect(*shape, *other);
            if (!intersection_resolution.supported) {
                cout << typeid(*shape).name() << " - " << typeid(*other).name() << " intersection resolving not supported" << endl;
            }
        }
    }
}
rectangle drawn
triangle drawn
circle drawn
rectangles intersect
9Rectangle - 8Triangle intersection resolving not supported
rectangle intersect circle
8Triangle - 9Rectangle intersection resolving not supported
8Triangle - 8Triangle intersection resolving not supported
8Triangle - 6Circle intersection resolving not supported
rectangle intersect circle
6Circle - 8Triangle intersection resolving not supported
6Circle - 6Circle intersection resolving not supported