C ++将基类指针转换为派生类,而不需要知道派生类

时间:2015-03-02 17:33:39

标签: c++ pointers casting derived-class base-class

我有各种类型的对撞机类(SphereCollider,AABBCollider等),都是从基类Collider继承而来的。我使用指向std :: vector中基类的指针来存储它们。

std::vector<std::shared_ptr<Collider>> colliders;

然后我有各种各样的功能来确定对撞机是否相互交叉。

bool Colliding(const SphereCollider& colliderOne, const SphereCollider& colliderTwo);
bool Colliding(const AABBCollider& colliderOne, const AABBCollider& colliderTwo);
bool Colliding(const AABBCollider& colliderOne, const SphereCollider& colliderTwo);
bool Colliding(const SphereCollider& colliderOne, const AABBCollider& colliderTwo);

我的想法是简单地遍历对撞机矢量,然后将每个碰撞器与列表中的所有其他碰撞器进行比较。

for (unsigned int a = 0; a < colliders.size(); ++a)
{
    for (unsigned int b = a + 1; b < colliders.size(); ++b)
    {
        // Store the two colliders we're current comparing
        std::shared_ptr<Collider> colliderOne = colliders[a];
        std::shared_ptr<Collider> colliderTwo = colliders[b];

        // Somehow cast pointers back to correct derived type

        // Check colliding
        if(Colliding(*colliderOneDerived, *colliderTwoDerived))
        {
            // Colliding
        }

    }
}

但要成功完成此操作,我需要将指针转换回正确的派生类型,以便调用正确的函数,但我不知道每次碰撞器的类型。

基础碰撞器类有一个Type()函数,它正确地从基指针返回派生类型。

std::type_index Collider::Type() { return typeid(*this); }

有没有办法可以使用它来将指针强制转换为正确的派生类型?

4 个答案:

答案 0 :(得分:1)

您想要做的是称为双重调度,C ++本身不支持。您可以考虑以下几个选项:

你可以用虚函数来实现它,这意味着添加虚函数,将每种类型的对象带到每个对象。这将导致相对快速的查找,但实现它的努力呈二次方式增长。

class Obj1;
class Obj2;

class Base{
    virtual bool collide(Base* p ) =0;
    virtual bool collide(Obj1*) = 0;
    virtual bool collide(Obj2*) = 0;
};

class Obj1{
    virtual bool collide(Base* p) override { return p->collide(this); }
    virtual bool collide(Obj1* p) override { return colliding(p,this); }
    virtual bool collide(Obj2* p) override;
};

class Obj2{
    virtual bool collide(Base* p) override { return p->collide(this); }
    virtual bool collide(Obj1* p) override { return colliding(p,this); }
    virtual bool collide(Obj2* p) override { return colliding(p,this); }
};

bool Obj1::collide(Obj2* p) override { return colliding(p,this); }

你也可以使用Visitor pattern获得同样的效果。

您可以向每个返回对象typeid的对象添加一个虚函数,并生成由一对std::type_index索引的函数指针的已排序平面映射。这将允许更大的灵活性,因为您可以在项目的多个位置向地图添加对象。

您可以为每个对象添加一个唯一的constexpr索引,每个对象都有一个虚函数返回该索引,并创建一个元编程,该编程在编译时从对象列表中生成查找表。这几乎与我的第一个例子一样快,但每个对象只需要一个虚函数,并且在程序的某一点上可以知道所有对象类型。用户将负责索引的唯一性,但元程序可以检测到双索引,从而减少错误的变化。

答案 1 :(得分:0)

使用dynamic_pointer_cast获取派生类的共享指针。如果派生类的类型错误,则返回的指针将为nullptr

std::shared_ptr<SphereCollider> sc = std::dynamic_pointer_cast<SphereCollider>(colliderOne);
如果sc不属于nullptr,则

colliderOne将为SphereCollider。否则它将指向SphereCollider

通过使用此方法,您可以实现动态分派。

<强>更新

以下是使用智能指针向下转换实现动态分派的示例。

std::shared_ptr<SphereCollider> sc1, sc2;
std::shared_ptr<AABBCollider> aabc1, aabc2;
...

if (sc1 = std::dynamic_pointer_cast<SphereCollider>(colliderOne))
{
    if (sc2 = std::dynamic_pointer_cast<SphereCollider>(colliderTwo))
        Colliding(*sc1, *sc2);
    else if (aabc2 = std::dynamic_pointer_cast<AABBCollider>(colliderTwo))
        Colliding(*sc1, *aabc2);
    ...
}
else if (aabc1 = std::dynamic_pointer_cast<AABBCollider>(colliderOne))
{
    if (sc2 = std::dynamic_pointer_cast<SphereCollider>(colliderTwo))
        Colliding(*aabc1, *sc2);
    else if (aabc2 = std::dynamic_pointer_cast<AABBCollider>(colliderTwo))
        Colliding(*aabc1, *aabc2);
    ...
}
...

使用哈希表

优化动态分派

如果你有许多不同类型的Collider类,上面的代码可能效率不高。它在类中执行线性搜索以执行调度。

通过向返回类名的Collider类添加一个字段,您可以连接两个类名来构建一个键。然后你会使用 std::unordered_map以lambda作为关联值。 lambda会收到两个std::shared_ptr<Collider>作为参数。它会执行向下转换并调用Collider函数。

如果需要,我会显示代码。

答案 2 :(得分:0)

您可以为将在循环中调用的基类Colliding提供一般Collider函数。该通用函数基于传递的对象的动态类型进行区分,从而基于动态类型模拟过载。一个或多或少丑陋的宏可以帮助减少代码。

#define call_dynamic(T1,T2,o1,o2) if( dynamic_cast<T1*>(&o1) && dynamic_cast<T2*>(&o2) ) return Colliding( *((T1*)&o1),*((T2*)&o2) )

bool Colliding(const Collider& colliderOne, const Collider& colliderTwo) {
  call_dynamic(SphereCollider,SphereCollider,colliderOne,colliderTwo);
  /***/
  call_dynamic(SphereCollider,AABBCollider,colliderOne,colliderTwo);
  /* this line should never be reached */
  return false;
}

根据您拥有的课程数量,这仍然需要一些代码行。如果性能很重要,这也不是最佳解决方案。但至少这是解决问题的一种可能性。

如果将对象传递给函数的顺序无关紧要,则可以进行一些改进。

答案 3 :(得分:0)

另一种方法是将Colliding函数移动到类中,并使用Double Dispatch方法解析动态类型。

这适用于c1.collideWith(c2);如下:

  1. c1是一个引用,因此解析为其动态类型(此处为:SphereCollider)。因此,调用方法SphereCollider::collideWith(Collider& c )

  2. 要同时解析现在位于c2的{​​{1}}类型,我们再次调用成员函数(此时为c)以强制进行类型解析。我们将c2作为现在属于*this类型的参数传递。这将调用方法SphereCollider

  3. 所以你唯一需要的是对你的对象的两个引用,这个方法将起作用。下面给出一个例子。

    AABBCollider::collideWith(SphereCollider& c )

    输出如下:

    #include <iostream>
    
    class SphereCollider;
    class AABBCollider;
    
    class Collider {
    public:
      virtual bool collideWith(Collider& c ) {
        std::cout << "Collider <> Collider" << std::endl;
      };
      virtual bool collideWith(SphereCollider& c ) {};
      virtual bool collideWith(AABBCollider& c ) {};
    };
    
    class SphereCollider : public Collider {
    public:
      virtual bool collideWith(Collider& c ) {
        c.collideWith(*this);
      }
    
      virtual bool collideWith(SphereCollider& c ) {
        std::cout << "SphereCollider <> SphereCollider" << std::endl;
      }
    
      virtual bool collideWith(AABBCollider& c ) {
        std::cout << "AABBCollider <> SphereCollider" << std::endl;
      }
    };
    
    class AABBCollider : public Collider {
    public:
      virtual bool collideWith(Collider& c ) {
        c.collideWith(*this);
      }
    
      virtual bool collideWith(AABBCollider& c ) {
        std::cout << "AABBCollider <> AABBCollider" << std::endl;
      }
    
      virtual bool collideWith(SphereCollider& c ) {
        std::cout << "SphereCollider <> AABBCollider" << std::endl;
      }
    };
    
    int main( int argc, char **argv ) {
    
      Collider& c1 = * new SphereCollider;
      Collider& c2 = * new AABBCollider;
    
      c1.collideWith(c1);
      c1.collideWith(c2);
      c2.collideWith(c2);
      c2.collideWith(c1);
    
      return 0;
    }