从抽象基类指针转换为派生类

时间:2015-04-09 23:55:52

标签: c++ inheritance

我试图为我的游戏创建一个州经理,我有4个班级:

游戏状态:

  // 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() { }
  };

StateManager:

  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);
  }
}

MainMenu:

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())

我可以在Game中访问类MainMenu的唯一方法是通过强制转换指针吗?

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

这是对的吗?有些东西告诉我,我做错了。

2 个答案:

答案 0 :(得分:3)

immibis 的答案非常适合解决技术演员问题。

然而,你的设计中有些奇怪的东西。所以我想提供一个替代答案,解决设计问题。

首先StateManager本身不是GameState。因此,update()draw()不需要具有相同的签名。你有没有预见到这些StateManager函数中的一个在参数中被另一个StateManager调用了?对于Game,我认为这没有任何意义。所以我建议重构课程(并相应地调整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的{​​{1}}的Concerete实现可以直接使用它,但是应该仅将它用于特定于游戏的抽象。

主要的不便之处是:

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

答案 1 :(得分:1)

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

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

Game将包含空指针,否则它将包含指向游戏的指针。

如果您 确定它总是一个Game并且想要跳过检查(为了获得微小的性能增益),请使用:< / p>

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

如果stateManager没有指向Game,则会产生未定义的行为。