使用基类指针调用带有派生类参数的模板化函数

时间:2013-12-13 14:14:26

标签: c++ templates inheritance c++11

我有不同的几何,它们都是从基类派生的,以便在向量中收集它们。现在我想检测此向量中两个几何之间的碰撞。相交函数使用两种几何类型进行模板化(就我所知,多态性仅适用于一个对象)。

如何正确调用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库提供,所以我无法改变它......

4 个答案:

答案 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 ...
};

鉴于两个几何geom1geom2,您现在可以测试与

的碰撞
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

正在做三件事:

  1. geom1
  2. 获取一些值
  3. geom2
  4. 获取一些值
  5. 使用上述值进行数学处理并处理结果
  6. 如果是这样,请按照以下方式进行操作。

    首先,将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;