编辑:我正在和C++一起工作。
因此,我正在创建方法/函数来测试形状之间的交集。我基本上有这个:
class Shape {};
class Rectangle : public Shape {};
class Circle : public Shape {};
class Line : public Shape {};
现在,我需要决定编写实际方法/函数以测试交集的最佳方法。但是我所有的形状都将存储在 Shape 指针列表中,所以我将调用基本窗体的方法/函数:
bool intersects (Shape* a, Shape* b);
此时,我需要确定形状"a"和"b"的类型,以便我可以正确检测碰撞。我可以通过使用一些虚拟方法轻松完成其中之一:
class Shape
{
virtual bool intersects (Shape* b) = 0;
}
这将确定其中一个形状("a"现在是"this"(。但是,我仍然需要获得"b"的类型。显而易见的解决方案是给 Shape 一个"id"变量来对它是什么形状进行分类,然后"切换"这些变量,然后使用 dynamic_cast。但是,这不是很优雅,感觉应该有一种更OO的方式来做到这一点。
有什么建议吗?
正如@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;
};
然后,Shape
层次结构对此进行调整。我们需要两种方法:一种是接受任何类型的访客,另一种是创建"适当"的十字路口访客。
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;
};
例如,对于 Circle,它将给出:
#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;
};
如果其他方法需要双重调度机制,那么您需要做的就是创建另一个"类似交叉"的类,该类包装结果并从Visitor
继承,以及一个植根于Shape
的新"工厂"方法,该方法被派生类重写以提供适当的操作。这有点啰嗦,但确实有效。
注意:除了intersect(circle, rectangle)
和intersect(rectangle, circle)
之外,产生相同的结果是合理的。您可以将代码分解为一些方法,并具有具体实现的CircleIntersecter::visit
委托。这样可以避免代码重复。
Andrei Alexandrescu在他的经典现代C++设计中详细介绍了这个问题。配套库 Loki 包含多方法的实现。
更新
Loki根据用户的需求提供了三种多方法的实现。有些是为了简单,有些是为了速度,有些是为了低耦合,有些比其他的更安全。书中的这一章长达近40页,它假设读者熟悉本书的许多概念——如果你习惯使用boost,那么洛基可能会在你的小巷里。我真的不能把它提炼成一个可以接受的答案,但我已经向你指出了我所知道的C++对这个主题的最佳解释。
C++运行时多态性只有一个调度(基类vtable(。
你的问题有多种解决方案,但没有一个是"优雅的",因为它们都试图迫使语言做更多它可以原生支持的事情(Alexandrescu Loki multimethods是一组非常隐藏的黑客:它封装了"坏事",但并没有使它变得好(
这里的概念是,您需要编写可能组合的所有 N2 个函数,并根据 TWO 参数的实际运行时类型找到调用它们的方法。"访客模式"(从另一个虚拟函数回调虚拟函数(、"多方法"技术(使用通用数据分配表(、"动态投射"到虚函数或"双dynamic_cast"输出所有函数都做同样的事情:在两个间接之后调用一个函数。从技术上讲,它们都不能被定义为"优于另一个",因为由此产生的性能几乎相同。
但是其中一些在代码编写方面的成本高于另一个,而另一些在代码维护方面的成本更高。您最有可能尝试估计在您的情况下权衡是什么。您认为将来可能需要添加多少其他类?
您可以为每个Shape
添加一个字段shapeType
例如:
class Shape {
virtual shapetype_t getShapeType() const;
// ...
}
我玩过形状交集解析调度方法只是为了好玩。我不喜欢每次出现新形状时扩展类的想法。我想到了相交解析器的集合,它被迭代以找出是否有一个支持给定的形状对。如果出现新形状,则将新的相交解析器添加到集合中。
就性能而言,我不认为这是最佳方法,因为解析器会迭代并执行动态转换,直到找到合适的解析器。
但是,尽管如此...
相交解析器采用两种形状并返回包含支持标志和相交标志的解析结果。
struct Intersection_resolution {
bool supported;
bool intersect;
};
class IIntersection_resolver {
public:
virtual Intersection_resolution intersect(Shape& shape1, Shape& shape2) = 0;
};
解析器实现。模板类,采用两种形状,检查它是否支持它们,如果是,则调用check_intersection方法。后者应在规范中定义。请注意,该对应仅指定 1,即如果指定了矩形-圆形,则无需指定圆形-矩形。
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};
}
};
几个规格...
template<>
bool Intersection_resolver<Rectangle, Rectangle>::check_intersection(Rectangle& r1, Rectangle& r2) {
cout << "rectangles intersect" << endl;
return true;
}
template<>
bool Intersection_resolver<Rectangle, Circle>::check_intersection(Rectangle& r1, Circle& r2) {
cout << "rectangle intersect circle" << endl;
return true;
}
解析器集合。
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