模板对象的访问者模式

时间:2013-06-19 00:49:13

标签: c++ templates c++11 double-dispatch

我正在尝试为学术目的制作一个自定义碰撞引擎,而且我仍然坚持一般的c ++编程问题 我已经拥有了所有正常工作的几何形状,并且对于问题的范围我有这个功能:

template<typename lhs_geometry, typename rhs_geometry>
bool intersects( const lhs_geometry& lhs, const rhs_geometry& rhs )
{
    //returns true if does objects intersects 
    //(assume this functions works perfectly with every geometry type)
}

我还有以下课程,我需要完成实施

template<typename geometry_type>
class collidable_object
{
public:
    explicit collidable_object( geometry_type& geometry ) :
        m_geometry( geometry )
    {

    }

    ~collidable_object()
    {

    }

private:
    geometry_type& m_geometry;
};

我的问题出现的地方是我想创建一个collidable_object列表并测试它们的交叉点2乘以。

我已经对Google进行了一些研究,发现拥有collidable_object的基类将允许我将对象存储到列表中。但之后如何根据特定的几何形状测试对象?

我试图实现访问者模式,但每次都会卡住,因为我不想对每种可能的几何类型进行硬编码,因为我总是只调用intersetcs()

我还在cooperative visitor上发现了一篇文章,但这似乎很复杂。

有没有人有简单有效的解决方案?

编辑:我想避免使用几何列表的原因是因为我希望在不必在树枝中找到文件的情况下添加新几何图形相对容易。

EDIT2:这里有关于intersetcs方法的更多信息:intersects方法基于标签调度以找到正确的几何体但几乎所有凸形都使用GJK算法,该算法只需要对象可以返回点在给定方向上最远的。对于非凸形状,形状被分割成凸形子形状并且过程重新开始。

没有统一的标准来确定intersects是否能够处理给定的形状,大多数使用furthest_along但是球体上的球体没有,球体的分割也不需要使用{{1 }}

其他信息:我使用VS2012和C ++ 11

2 个答案:

答案 0 :(得分:2)

如果不在某些位置存储所有可能的几何图形列表,则无法逃脱。否则,编译器将不知道要生成哪个模板实例。但是我想出了一些代码,你必须在一个位置,即GeometryTypes的typedef中声明该列表。其他一切都建立在此之上。我这里没有使用vistor模式,这样做的好处是我不必将样板代码添加到不同的几何类实现中。为所有组合实施intersects就足够了。

首先包括:我稍后将使用shared_ptr,打印内容,并在未知几何类型的情况下中止。

#include <memory>
#include <iostream>
#include <cstdlib>

现在定义一些几何,使用一个可用于多态指针的公共基类。您必须至少包含一个虚拟函数,以便获得一个虚拟函数表,以后可以用于dynamic_cast。使析构函数具有多态性,即使通过多态指针删除,也可以确保派生类正确清理。

struct Geometry {
  virtual ~Geometry() { }
};
struct Circle : public Geometry { };
struct Rectangle : public Geometry { };

现在出现了intersects模板。我只为这个演示编写了一个catch-all实现。

template<typename lhs_geometry, typename rhs_geometry>
bool intersects(const lhs_geometry& lhs, const rhs_geometry& rhs) {
  std::cout << __PRETTY_FUNCTION__ << " called\n"; // gcc-specific?
  return false;
}

这是我们声明所有几何图形列表的地方。如果您有几何相互派生的,请确保首先使用最具特色的几何,因为这些将用于动态转换。

template<typename... Ts> class TypeList { };
typedef TypeList<Circle, Rectangle> GeometryTypes;

现在有一堆帮助代码。基本思想是迭代一个这样的TypeList并为每种类型尝试动态转换。第一个帮助器为lhs参数迭代,第二个为rhs参数。如果找不到匹配项,则表明您的列表不完整,这将导致应用程序中止,并显示有用的错误消息。

template<typename TL1, typename TL2> struct IntersectHelper1;
template<typename T1, typename TL2> struct IntersectHelper2;

template<typename TL2, typename T1, typename... Ts>
struct IntersectHelper1<TypeList<T1, Ts...>, TL2> {
  static bool isects(Geometry* lhs, Geometry* rhs) {
    T1* t1 = dynamic_cast<T1*>(lhs);
    if (!t1)
      return IntersectHelper1<TypeList<Ts...>, TL2>::isects(lhs, rhs);
    else
      return IntersectHelper2<T1, TL2>::isects(t1, rhs);
  }
};

template<typename T1, typename T2, typename... Ts>
struct IntersectHelper2<T1, TypeList<T2, Ts...>> {
  static bool isects(T1* lhs, Geometry* rhs) {
    T2* t2 = dynamic_cast<T2*>(rhs);
    if (!t2)
      return IntersectHelper2<T1, TypeList<Ts...>>::isects(lhs, rhs);
    else
      return intersects(*lhs, *t2);
  }
};

// Catch unknown types, where all dynamic casts failed:

bool unknownIntersects(Geometry* g) {
  std::cerr << "Intersection with unknown type: "
            << typeid(*g).name() << std::endl;
  std::abort();
  return false; // should be irrelevant due to abort
}

template<typename TL2>
struct IntersectHelper1<TypeList<>, TL2> {
  static bool isects(Geometry* lhs, Geometry* rhs) {
    return unknownIntersects(lhs);
  }
};

template<typename T1>
struct IntersectHelper2<T1, TypeList<>> {
  static bool isects(T1* lhs, Geometry* rhs) {
    return unknownIntersects(rhs);
  }
};

有了所有这些助手,您现在可以进行多态交叉测试。我正在引入一个shared_ptr来存储这样的多态指针,我建议你在collidable_object类中做同样的事情。否则,只要可碰撞对象存活,您就必须负责确保引用的几何图形保持活动状态,但最终会被清理干净。你想要那种责任吗?

typedef std::shared_ptr<Geometry> GeomPtr;

bool intersects(GeomPtr lhs, GeomPtr rhs) {
  return IntersectHelper1<GeometryTypes, GeometryTypes>::
    isects(lhs.get(), rhs.get());
}

最后一些主要的,所以你可以在一个小例子中实际运行上述所有代码。

int main() {
  GeomPtr g1(new Rectangle), g2(new Circle);
  std::cout << intersects(g1, g2) << std::endl;
  return 0;
}

答案 1 :(得分:0)

您的第二次编辑表明基本交叉例程将使用某些furthest_along代码运行。您可以使用这种方式,以便正常的交集检查对公共基类进行操作,该基类在其接口中包含此furthest_along。只有特殊情况才需要特殊功能,您需要其他算法。

以下示例避免了所有动态强制转换,并执行了两个虚拟方法调用(也称为“double dispatch”,顺便提一下,它也可用作标记,因此将其添加到您的问题可能有用)。

struct Geometry {
  virtual ~Geometry() { }
  virtual Point furthest_along(Vector& v) const = 0;
  virtual bool intersects(const Geometry& other) const {
    return other.intersects_impl(*this);
  }
  virtual bool intersects_impl(const Geometry& other) const { // default impl
    // compute intersection using furthest_along
  }
  virtual bool intersects_impl(const Circle& other) const {
    return intersects_impl(static_cast<const Geometry&>(other)); // use default
  }
};

struct Circle : public Geometry {
  bool intersects(const Geometry& other) const {
    return other.intersects_impl(*this); // call intersects_impl(const Circle&)
  }
  bool intersects_impl(const Circle& other) const {
    // do circle-circle intersection
  }
  Point furthest_along(Vector& v) const {
    // implement for default intersection
  }
};
struct Rectangle : public Geometry {
  Point furthest_along(Vector& v) const {
    // implement for default intersection
  }
};

如果您致电a.intersects(b),则intersects方法将从a的虚拟功能表中选择,而intersects_impl方法将从{{1}中选择}}。如果您要为类型组合bA添加特殊情况,则必须添加

  1. 虚拟方法B,委托默认
  2. 转移方法Geometry::intersects_impl(const A&)委托给A::intersects
  3. 具有实际自定义代码
  4. 的覆盖方法intersects_impl(const A&)

    如果必须添加许多具有许多特殊情况算法的类型,这可能相当于在不同地方进行了相当多的修改。但是,如果您添加的大多数形状都使用默认实现,那么您所要做的就是为每个形状正确实现B::intersects_impl(const A&)

    你当然可以做比这更聪明的事情。您可以创建一个使用furthest_along方法的中间类ConvexGeometry和一个类furthest_along,它可以提供一些分割成凸片的方法。您可以在这两者中实现NonConvexGeometry,并使intersects中的实现完全抽象(Geometry)。然后,您可以避免= 0,而是使用intersects_impl(const Geometry&)intersects_impl(const ConvexGeometry&)作为默认机制,intersects_impl(const NonConvexGeometry&)中的= 0 Geometry并在{{{}}中正确实施1}}和ConvexGeometry。但是如果您理解上述代码背后的想法,那么添加这些扩展应该足够简单。如果没有,请问。