解决此多态性问题的最优雅方法 编辑:我正在使用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