解决这种多态性问题的最优雅方式

时间:2012-01-26 06:01:48

标签: c++ polymorphism

编辑:我正在使用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'现在'这')。但是,我仍然需要获得' b'的类型。显而易见的解决方案是给Shape一个“id”#id;变量来分类它是什么形状,然后切换'通过那些,然后使用dynamic_cast。然而,这不是很优雅,感觉应该有更多的OO方式来做到这一点。

有什么建议吗?

5 个答案:

答案 0 :(得分:7)

正如@Mandarse所指出的,这是典型的双重调度问题。在面向对象的语言中,或者像可以实现面向对象概念的C ++语言一样,这通常使用Visitor模式来解决。

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

如果其他方法需要双重调度机制,那么您需要做的就是创建另一个“类似Intersecter”的类,它包装结果并继承自Visitor和一个新的“Factory”方法,其根植于{ {1}}被派生类覆盖以提供适当的操作。这有点啰嗦,但确实有效。

注意:除了Shapeintersect(circle, rectangle)之外,产生相同的结果是合理的。您可以将代码分解为某些方法,并将intersect(rectangle, circle)委托给具体实现。这可以避免代码重复。

答案 1 :(得分:4)

Andrei Alexandrescu在他的经典Modern C++ Design中详述了这个问题。配套库Loki包含the implementation for Multi-Methods

<强>更新

Loki提供了多种方法的三种实现,具体取决于用户的需求。有些是为了简单,有些是为了速度,有些是低耦合,有些提供比其他更安全。本书的这一章跨越了近40页,并假设读者熟悉本书的许多概念 - 如果你习惯使用提升,那么Loki可能就在你的小巷里了。我真的无法将其提炼为SO可接受的答案,但我已经向您指出了我所知道的C ++主题的最佳解释。

答案 2 :(得分:2)

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

你的问题有各种各样的解决方案,但没有一个是“优雅的”,因为他们都试图强迫语言做更多它本身支持(Alexandrescu Loki multimethods是一个非常好隐藏的黑客集合:它封装“糟糕的事情”,但并没有那么好)

这里的概念是,您需要编写可能组合的所有N 2 函数,并根据TWO参数的实际运行时类型找到调用它们的方法。 “访问者模式”(从另一个虚拟函数回调虚拟代理),“mutimethod”技术(使用通用dspatch表),“动态强制转换”成虚拟函数或“双动态广播”出所有函数都做同样的事情:在两个间接后调用一个函数。从技术上讲,它们都不能被定义为“比其他更好”,因为最终的性能基本相同。

但是其中一些在代码编写方面的成本高于另一方,而在代码维护方面的成本更高。 在您的情况下,您最有可能尝试估计权衡取舍。您认为以后可能需要添加多少个其他

答案 3 :(得分:1)

您可以为每个shapeType

添加字段Shape

例如:

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

答案 4 :(得分:0)

我玩过形状交集解析派遣方法只是为了好玩。我不喜欢每次出现新形状时都扩展类的想法。我想到了交集解析器的集合,该集合被迭代以找出是否存在一个支持给定形状的对。如果出现新形状,则将新的相交解析器添加到集合中。

就性能而言,我认为这不是最理想的方法,因为解析器会反复迭代并执行动态强制转换,直到找到合适的解析器为止。

但是,尽管如此...

相交解析器采用两种形状并返回包含受支持和相交标志的解析结果。

struct Intersection_resolution {
    bool supported;
    bool intersect;
};

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

解析器实现。模板类采用两种形状,检查是否支持它们,并调用check_intersection方法。后期应在规范中定义。请注意,该对应该仅指定一对,即如果指定了Rectangle-Circle,则无需指定Circle-Rectangle。

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