我有不同的几何,它们都是从基类派生的,以便在向量中收集它们。现在我想检测此向量中两个几何之间的碰撞。相交函数使用两种几何类型进行模板化(就我所知,多态性仅适用于一个对象)。
如何正确调用intersect()?有没有没有dynamic_cast和检查的方法!= nullptr?将枚举保存为几何类中的constexpr并使用static_cast?
非常感谢。
enum class GeometryType
{
BOX,
SPHERE,
CAPSULE,
CONE,
CYLINDER,
HALFSPACE
};
class GeometryBase
{
public:
GeometryBase() {}
virtual ~GeometryBase() {}
};
template<enum GeometryType GeomType>
class Geometry : public GeometryBase
{
public:
Geometry() {}
virtual ~Geometry() {}
};
template<enum GeometryType GeomType1, enum GeometryType GeomType2>
void intersect(Geometry<GeomType1>* geom1, Geometry<GeomType2>* geom2)
{
// math stuff
}
void detectCollisions(GeometryBase* geomBase1, GeometryBase* geomBase2)
{
// what can I do here to call the correct intersect<...,...>(...,...)?
}
编辑:交叉功能由FCL库提供,所以我无法改变它......
答案 0 :(得分:4)
多态性通常应该比检查类型的switch语句更受欢迎,因为生成的代码更加类型安全,并且您得到编译时错误而不是运行时错误,这通常是一件好事。你有一个函数的有趣情况,它接受两个对象,并且应该根据两种参数类型动态调度。
以下是一种方法:首先,您需要转发声明所有派生类并以下列方式编写基类:
class Box;
class Sphere;
class Cone;
// ...
class GeometryBase
{
public:
virtual bool collidesWith( const GeometryBase & other ) const = 0;
protected:
virtual bool dispatchCollidesWith( const Box & other ) const = 0;
virtual bool dispatchCollidesWith( const Sphere & other ) const = 0;
virtual bool dispatchCollidesWith( const Cone & other ) const = 0;
// ...
};
必须实施函数collidesWith()
才能以dispatchCollidesWith()
为other
作为参数调用*this
。请注意,*this
在派生类中具有不同的类型,因此会调用不同的重载。为了省略太多的输入,我们使用了一个为我们实现的模板:
template <typename T>
class GeometryImpl : public GeometryBase
{
public:
virtual bool collidesWith( const GeometryBase & other ) const
{
assert( typeid(*this) == typeid(T) );
return other.dispatchCollidesWith( static_cast<const T&>(*this) );
}
};
现在可以通过以下方式实现派生类:
class Box : public GeometryImpl<Box>
{
protected:
virtual bool dispatchCollidesWith( const Box & other ) const { /* do the math */ }
virtual bool dispatchCollidesWith( const Sphere & other ) const { /* do the math */ }
virtual bool dispatchCollidesWith( const Cone & other ) const { /* do the math */ }
// ...
private:
// data ...
};
鉴于两个几何geom1
和geom2
,您现在可以测试与
geom1.collidesWith( geom2 );
一切都是完全类型安全的。
这种方法有一个缺点:你必须向你的基类添加大量的函数,它会很容易拥挤。以下是如何使基类可以扩展为虚拟调度,因此每次需要新功能时都不需要为其添加虚函数:
class GeometryDispatcher;
class GeometryBase
{
public:
void run( GeometryDispatcher & dispatcher ) const = 0;
};
class GeometryDispatcher
{
public:
virtual void dispatch( const Box & shape ) = 0;
virtual void dispatch( const Sphere & shape ) = 0;
virtual void dispatch( const Cone & shape ) = 0;
};
通过从GeometryDispatcher
派生,您可以向类层次结构添加新功能。 run()
函数必须由GeometryBase
的派生类以下列方式实现:
void Box::run( GeometryDispatcher & dispatcher ) const
{
dispatcher.dispatch( *this );
}
(这也称为访问者模式的前半部分。阅读它,以便您可以理解我的代码!)现在您可以通过以下方式添加函数collidesWithBox()
class CollidesWithBoxDispatcher : public GeometryDispatcher
{
public:
CollidesWithBoxDispatcher( const Box & box ) : box(box) {}
bool getResult() const { return result; }
virtual void dispatch( const Box & shape ) { ... }
virtual void dispatch( const Sphere & shape ) { ... }
virtual void dispatch( const Cone & shape ) { ... }
private:
bool result;
const Box & box;
};
bool collidesWithBox( const GeometryBase & shape, const Box & box )
{
CollidesWithBoxDispatcher d( box );
shape.run( d );
return d.result;
}
您可以类似地实现函数collidesWithSphere()
和collidesWithCone()
。最后,您可以通过以下方式实现函数collidesWith()
:
class CollidesWithDispatcher : public GeometryDispatcher
{
public:
CollidesWithDispatcher( const GeometryBase & shape ) : shape(shape) {}
bool getResult() const { return result; }
virtual void dispatch( const Box & box )
{
result = collidesWithBox( shape, box );
}
virtual void dispatch( const Sphere & sphere ) { ... }
virtual void dispatch( const Cone & cone ) { ... }
private:
bool result;
const GeometryBase & shape;
};
bool collidesWith( const GeometryBase & shape1, const GeometryBase shape2 )
{
CollidesWithDispatcher d( shape2 );
shape1.run( d );
return d.result;
}
要编写大量代码,但是通过这种方式可以通过促进访问者模式获得双重调度。好结局。 :)
答案 1 :(得分:0)
在intersect
中,我猜测:
// math stuff
正在做三件事:
geom1
geom2
如果是这样,请按照以下方式进行操作。
首先,将intersect
设为非template
函数,该函数包含两个类型为GeometryBase*
的参数。
向virtual
添加纯GeometryBase
方法,该方法定义了一个返回&#34;值&#34;的接口。在步骤1和步骤1中需要2以上。您可能需要考虑如何以足够通用的方式最好地表示这些值,以便所有子类都可以返回相同类型的事物。
在每个virtaul
派生的具体类中实现纯GeometryBase
。
在调用这些虚拟方法方面实现intersect
(通过GeometryBase
指针;无需以任何方式强制转换或应用访问者模式。)
你可以让intersect
成为一个自由函数(在namespace
中,请!),或者可以在一个类中实现它。它可能(或可能不)使其成为GeometryBase
的成员函数,在这种情况下this
将用于其中一个GeometryBase*
参数的leiu。
答案 2 :(得分:0)
我接近这个的方法是在类型映射的帮助下进行手动双重调度。
这是一个非常简单的版本:
class GeometryBase
{
public:
GeometryBase() {}
virtual GeometryBase() {}
virtual void doIntersect( GeometryBase* other ) = 0;
virtual GeometryType GetGeometryType() const = 0;
};
// forward decl:
template<enum GeometryType GeomType1, enum GeometryType GeomType2>
void intersect(Geometry<GeomType1>* geom1, Geometry<GeomType2>* geom2);
template<enum GeometryType GeomType>
class Geometry : public GeometryBase
{
public:
Geometry() {}
virtual Geometry() {}
GeometryType GetGeometryType() const { return GeomType; }
virtual void doIntersect( GeometryBase* other ) {
switch (other->GetGeometryType()) {
case GeometryType::BOX:
intersect( this, static_cast<GeometryType<GeometryType::BOX>*>(other) );
break;
// etc
}
}
};
我们在switch语句中进行手动动态双重调度。
我们可以构建一些基础架构,以便在很多方面实现这一目标。您可以编写一个fast_cast
,它支持仅使用virtual
函数查找和调用(通常比dynamic_cast
更快)将基类转换为多个子类。你可以用一个魔术开关来编写一个替换上面复制/粘贴case
表达式的函数(但是这样做的样板比上面的代码要长)。
您可以编写一个宏来写出case
(从而服从DRY,并减少某些类别的错误)。
但基本问题是双重调度,双重调度需要C ++程序员手动工作。
答案 3 :(得分:0)
您应该更好地使用继承而不是使用枚举。然后你可以创建一个泛型交叉函数,它将GeometryBase对象作为参数:它对于GeometryBase子项是通用的。
这意味着您必须在GeometryBase中提供所需的接口才能计算交集:
class GeometryBase
{
//...
public: // your interface
virtual Vertices getVertices() const; // can be virtual, or not
//...
}
void intersect(GeometryBase* geom1, GeometryBase* geom2)
{
// math stuff, calling geom1->getVertices() & ...
}
//specializations
class Box: public GeometryBase
{
public:
virtual Vertices getVertices() const; // implement what you need to specialize
}
class Cone : public GeometryBase
{
}
// etc...
然后你的矢量应该是这样的:
std::vector<GeometryBase*> geometries;