模板对象的类似访问者的模式

visitor-like pattern for template objects

本文关键字:访问者 模式 对象      更新时间:2023-10-16

我正在尝试为学术目的制作一个自定义碰撞引擎,但我被困在一个一般的 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 x 2 交叉时。

我在 Google 上做了一些研究,发现有一个基类来collidable_object将允许我将对象存储到列表中。但是在那之后,我如何根据物体的特定几何形状测试物体?

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

我还找到了一篇关于合作访客的文章,但这似乎很复杂。

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

编辑:我想避免拥有几何列表的原因是,我希望添加新几何图形相对容易,而不必在植物园中查找文件。

EDIT2:以下是有关intersetcs方法的更多信息:相交方法基于标签调度来找到正确的几何形状,但几乎所有凸形都使用GJK算法,该算法仅要求对象可以返回给定方向上最远的点。对于非凸形状,形状将分割成凸子形状,然后重新开始该过程。

没有统一的标准来查看intersects是否能够处理大多数使用的给定形状furthest_along但球体上球体没有,球体膨胀也不需要使用furthest_along

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

如果不将所有可能的几何图形的列表存储在某个地方,您将无法逃脱。否则,编译器将不知道要生成哪个模板实例化。但是我想出了一些代码,您必须仅在一个位置(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模板。我只为这个演示编写了一个包罗万象的实现。

template<typename lhs_geometry, typename rhs_geometry>
bool intersects(const lhs_geometry& lhs, const rhs_geometry& rhs) {
  std::cout << __PRETTY_FUNCTION__ << " calledn"; // 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;
}

第二次编辑表明基本交集例程将使用一些furthest_along代码进行操作。你可以利用它,以这样的方式,正常的交集检查在一个公共基类上运行,该基类在其接口中包含这个furthest_along。您只需要特殊函数才能用于特殊情况,为此您需要其他算法。

以下示例避免了所有动态强制转换,而是执行了两次虚拟方法调用(也称为"双重调度",顺便说一下,它也可以用作双重调度标记,因此将其添加到您的问题中可能会很有用(。

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方法将从b中选择。如果要为类型组合添加特殊情况 AB ,则必须添加

  1. 一个虚拟方法Geometry::intersects_impl(const A&) ,委托给默认值
  2. 委托给
  3. intersects_impl(const A&) A::intersects重写方法
  4. 使用实际自定义代码B::intersects_impl(const A&)的重写方法

如果必须使用许多特殊情况算法添加许多类型,则可能相当于在不同位置进行相当多的修改。但是,如果添加的大多数形状都将使用默认实现,则您所要做的就是为每个形状正确实现furthest_along

你当然可以做比这更聪明的事情。您可以创建一个使用 furthest_along 方法的中间类ConvexGeometry,以及一个类NonConvexGeometry,该类将提供一些划分为凸块的方法。您可以在这两个中实现intersects,并在纯抽象Geometry实现(= 0(。然后,您可以避免intersects_impl(const Geometry&),而是使用 intersects_impl(const ConvexGeometry&)intersects_impl(const NonConvexGeometry&) 作为默认机制,这两者都可以在Geometry= 0并在 ConvexGeometryNonConvexGeometry 中适当地实现。但是,如果您了解上述代码背后的思想,那么添加这些扩展应该足够简单。如果没有,请询问。