使同类容器中的派生 类 以不同方式相互交互

Make derived classes in homogeneous container interect differently with each other

我在编写推箱子游戏时遇到了一个意想不到的问题,当时我不得不处理多态性。 这是我简化的 class 层次结构:

#include <iostream>
#include <vector>
#include <memory>

using namespace std;

class GameObject
{
public:
    virtual void onContact(GameObject* otherObject)
    {
        cout << "object met object" << endl;
    }
};

class Crate;
class Wall;
class Hero;

class Crate: public GameObject
{
public:
    virtual void onContact(Wall* w)
    {
        cout << "crate met wall" << endl;
    }
    virtual void onContact(Hero* h)
    {
        cout << "crate met hero" << endl;
    }
};

class Wall: public GameObject
{
public:
    virtual void onContact(Crate* c)
    {
        cout << "wall met crate" << endl;
    }
    virtual void onContact(Hero* h)
    {
        cout << "wall met hero" << endl;
    }
};


class Hero: public GameObject
{
public:
    virtual void onContact(Crate* crate)
    {
        cout << "hero met crate" << endl;
    }
    virtual void onContact(Wall* w)
    {
        cout << "hero met wall" << endl;
    }
};

int main()
{
    //Works
    auto hero = unique_ptr<Hero>(new Hero());
    auto crate = unique_ptr<Crate>(new Crate());
    auto wall = unique_ptr<Wall>(new Wall());

    hero->onContact(crate.get()); // "hero met crate"
    hero->onContact(wall.get()); // "hero met wall"
    crate->onContact(wall.get()); // "crate met wall"
    wall->onContact(crate.get()); // "wall met crate"

    cout << endl;

    //Problem: in the program the game objects are stored in a vector (homogeneous container)
    vector<unique_ptr<GameObject>> gameObjects;
    gameObjects.push_back(move(hero));
    gameObjects.push_back(move(crate));
    gameObjects.push_back(move(wall));

    /* 
    "object met object".
    Should be "hero met crate".
    That's because all objects in vector are GameObject.
    */
    gameObjects[0]->onContact(gameObjects[1].get());

    /* "object met object", should be "wall met crate" */
    gameObjects[2]->onContact(gameObjects[1].get());

    return 0;
}

所以这是我的 question/problem:由于大多数(所有?)stl 容器是同类的,所有派生对象都存储为基础对象(在我的例子中是 GameObject),因此根据类型阻碍了适当的多态性onContact 方法中的参数。

如何在不动态转换(并检查有效的方法)的情况下恢复到原始类型? class 设计有缺陷吗?

感谢您的帮助。

TL;DR:如何在彼此不同交互的派生对象的同质集合中优雅地应用多态性?

要点:https://gist.github.com/gaultier/e437877395b5831a0623

你需要某种双重调度机制。一种可能性是使用访问者模式在两个对象的运行时类型上分派你的碰撞器。

将默认行为放入您的基础 class:

class GameObject
{
    public:
  virtual void contactHandlerDefault(GameObject* otherObject)
  {
    cout << "met an other object";
  }

  virtual void contactHandlerCrate(Crate* o);
  virtual void contactHandlerWall(Wall* o);
  virtual void contactHandlerHero(Hero* o);

  virtual void onContact(GameObject* otherObject)
  {
    otherObject->contactHandlerDefault(this);
  }
};

然后,对于每个派生的 classes,覆盖任何你想要的碰撞处理程序,然后让它们将它们的 this 指针转换为正确的静态类型,并将它们自己转发到正确的处理程序中碰撞对象:

class Hero: public GameObject
{
public:
  virtual void onContact(GameObject* otherObject) override
  {
    otherObject->contactHandlerHero(static_cast<Hero*>(this));
  }

  virtual void contactHandlerDefault(GameObject* obj) override
  {
    cout << "do nothing, I can't go through";
  }

  virtual void contactHandlerCrate(Crate* crate) override
  {
    cout << "pushing the crate";
  }
};

然后使基础 class 处理程序转发到默认处理程序(您可以通过重载来完成此操作,这意味着您不需要这样做,但是如果您这样做,则会出现编译错误添加一个新的 class 并且不向其添加处理程序):

void GameObject::contactHandlerCrate(Crate* o)
{
  contactHandlerDefault(o);
}
//...

现在调用 gameObjects[1]->onContact(gameObjects[0]); 输出 pushing the crate。请注意,此方法意味着您需要反转调用,因此您调用的不是 hero->onContact(crate),而是 crate->onContact(hero).

感谢@TartanLlama,我正确地实施了它。 我还了解到它被称为双重调度:https://en.wikipedia.org/wiki/Double_dispatch

唯一需要注意的是相反的顺序:

Note that this method means that you need to reverse your calls, so instead of calling hero->onContact(crate), you call crate->onContact(hero).

代码如下:

#include <iostream>
#include <vector>
#include <memory>

using namespace std;

class Crate;
class Wall;
class Hero;

class GameObject
{
public:
    virtual void onContact(GameObject* o)
    {
        o->onContactHandler(this);
    }
    virtual void onContactHandler(GameObject* o)
    {
        cout << "object met object" << endl;
    }
    virtual void onContactHandler(Hero* h)
    {
        cout << "object met hero" << endl;
    }
    virtual void onContactHandler(Wall* w)
    {
        cout << "object met wall" << endl;
    }
    virtual void onContactHandler(Crate* c)
    {
        cout << "object met crate" << endl;
    }
};

class Crate: public GameObject
{
public:
    virtual void onContact(GameObject* o) override
    {
        o->onContactHandler(static_cast<Crate*>(this));
    }
    virtual void onContactHandler(Wall* w) override
    {
        cout << "crate met wall" << endl;
    }
    virtual void onContactHandler(Hero* h) override
    {
        cout << "crate met hero" << endl;
    }
};

class Wall: public GameObject
{
public:
    virtual void onContact(GameObject* o) override
    {
        o->onContactHandler(static_cast<Wall*>(this));
    }
    virtual void onContactHandler(Crate* c) override
    {
        cout << "wall met crate" << endl;
    }
    virtual void onContactHandler(Hero* h) override
    {
        cout << "wall met hero" << endl;
    }
};


class Hero: public GameObject
{
public:
    virtual void onContact(GameObject* o) override
    {
        o->onContactHandler(static_cast<Hero*>(this));
    }
    virtual void onContactHandler(Crate* c) override
    {
       cout << "hero met crate" << endl;
    }
    virtual void onContactHandler(Wall* w) override
    {
        cout << "hero met wall" << endl;
    }
};



int main()
{
    auto hero = unique_ptr<Hero>(new Hero());
    auto crate = unique_ptr<Crate>(new Crate());
    auto wall = unique_ptr<Wall>(new Wall());

    hero->onContact(crate.get()); // "crate met hero"
    hero->onContact(wall.get()); // "wall met hero"
    crate->onContact(wall.get()); // "wall met crate"
    wall->onContact(crate.get()); // "crate met wall"

    cout << endl;

    vector<unique_ptr<GameObject>> gameObjects;
    gameObjects.push_back(move(hero));
    gameObjects.push_back(move(crate));
    gameObjects.push_back(move(wall));

    gameObjects[0]->onContact(gameObjects[1].get()); //crate met hero
    gameObjects[2]->onContact(gameObjects[1].get()); //crate met wall

    return 0;
}

谢谢!

要点:https://gist.github.com/gaultier/e437877395b5831a0623(第二个文件)