从抽象基础 class 指针转换为派生 class

Casting from abstract base class pointer to derived class

我正在尝试为我的游戏创建一个状态管理器,我有 4 个 classes:

游戏状态:

  // foward declaration to avoid circular-referency
  class StateManager;

  class GameState
  {
    public:
      virtual ~GameState() { }    
      virtual void update(StateManager* gameManager) = 0;
      virtual void draw(StateManager* gameManager) = 0;

   protected:
      GameState() { }
  };

状态管理器:

  class StateManager
  {
    public:
      StateManager();
      virtual ~StateManager();

      void addState(GameState* gameState);
      void update(StateManager* stateManager);
      void draw(StateManager* stateManager);

    protected:
      // store states in a unique_ptr to avoid memory leak
      std::vector<std::unique_ptr<GameState> > states_;
  };

游戏:

class Game : public StateManager
{
public:
  void compute()
  {
    // call methos of statemanager
    update(this);
    draw(this);
  }
}

和主菜单:

class MainMenu : public GameState
{
  public:
    // override the pure virtual methos of GameState
    void update(StateManager* stateManager)
    {
      // problem here.
      // I need to handle instance of Game in this class, 
      // but the pointer is one StateManager
    }
    void draw(StateManager* stateManager) {}
}

当我像这样初始化我的游戏时:game.addState(new MainMenu())

我可以访问 MainMenu 中的 class Game 的唯一方法是通过转换指针?

// MainMenu class
void update(StateManager* stateManager)
{
    Game* game = (Game*) stateManager;
    game.input.getKey(ANY_KEY);
    //...
}

这样对吗?有些东西告诉我我做错了。

如果您不确定 stateManager 是否指向 Game,请使用:

Game *game = dynamic_cast<Game*>(stateManager);
如果 stateManager 没有指向 Game

game 将包含一个空指针,否则它将包含一个指向游戏的指针。

如果您确定它总是一个Game并且想跳过检查(以获得微小的性能提升) , 使用:

Game *game = static_cast<Game*>(stateManager);

如果 stateManager 不指向 Game.

将产生未定义的行为

immibis 的答案非常适合解决技术铸造问题。

不过,您的设计有些奇怪。所以我想提供一个替代答案,解决设计问题。

首先 StateManager 本身不是 GameState。因此 update()draw() 不需要相同的签名。您是否曾预见到这些 StateManager 函数中的一个会在参数中与另一个 StateManager 一起调用?对于Game,我认为这没有意义。所以我建议重构 class(并相应地调整 Game):

class StateManager {
public:
    ...
    void update();  // they always know "this".  
    void draw();
protected:
    ...
};

接下来,StateManager 似乎拥有 GameState(使用 unique_ptr<> 的受保护矢量,您的铸造问题表明了这一点)。所以另一种设计也可能很有趣:

class GameState {
public:
    virtual ~GameState() { }    
    virtual void update() = 0;
    virtual void draw() = 0;
protected:
    GameState(StateManager* gameManager) { }  // GameStates are created for a StateManager
    StateManager* gm;   // this manager can then be used for any GameState functions that need it
};

按照这个逻辑,MainMenu 将重构为:

class MainMenu : public GameState
{
public:
    MainMenu (Game* g) : game(g), GameState(g) {}
    void update()
    {
        // NO LONGER NEEDED:  Game* game = (Game*) stateManager;
        game->input.getKey(ANY_KEY);
        // NO MORE PROBLEMS HERE:  you always refer to the right object without any overhead
    }
    void draw() {}
protected:  
    Game *game;   // the owning game.  
};  

这种替代设计的优点是:

  • 代码会更简洁,也更不容易出错,因为您不必总是担心成员函数的参数。
  • StateManager 指针仅用于 StateManager 抽象。
  • GameState 的具体实现依赖于 Game,可以直接使用它,但应该只将它用于特定于游戏的抽象。

主要不便之处有:

  • GameState 必须始终为一个特定的 StateManager 创建。
  • 设计的稳健性是以所有 GameState 实现(例如 MainMenu)中的冗余指针为代价的。如果你有数百万个,它可能会成为一个内存问题。