【问题标题】:Casting from abstract base class pointer to derived class从抽象基类指针转换为派生类
【发布时间】:2015-04-09 23:55:51
【问题描述】:

我正在尝试为我的游戏创建一个状态管理器,我有 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() { }
  };

状态管理器:

  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 中的类Game 的唯一方法是强制转换指针?

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

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

【问题讨论】:

  • “某事告诉我我做错了。”那是什么?
  • 每个人都想要多态性的所有好处与具体类的所有好处相结合,这很混乱。您需要将dynamic_cast 您的StateManager* 转换为Game*。失败时返回nullptr
  • StateManager::updateStateManager::draw 将两个 StateManager 对象作为输入 - 这有意义吗?我会将addState 更改为void addState(std::unique_ptr&lt;GameState&gt;); 以明确它需要所有权。
  • 没有必要,在我的代码中它们不存在。 game.addState(new Level ()) 不是比这更好吗:std::unique_ptr &lt;gamestate&gt; state(new Level ()); game.addState (state); ??
  • 为什么StateManager updatedraw 还要把StateManager* 作为参数呢?而且您在那里使用的是普通指针,因此很难说这些方法背后的意图是什么。共享所有权、传递、回调?通常向下铸造表示设计问题/缺陷。你保证对象是Derived 类型吗?如果是这样,您为什么将其传递为Base。如果不是,为什么不呢?为什么在你真正需要的时候允许自己“丢失”(你丢失静态的)类型信息。

标签: c++ inheritance


【解决方案1】:

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&lt;&gt; 的受保护向量,您的铸造问题暗示了这一点)。所以另一种设计也可能很有趣:

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 抽象。
  • 依赖GameGameState 的具体实现可以直接使用它,但只能将它用于游戏特定的抽象。

主要的不便之处是:

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

【讨论】:

    【解决方案2】:

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

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

    如果stateManager 没有指向Game,则game 将包含一个空指针,否则它将包含一个指向游戏的指针。

    如果您确定始终Game 并且想要跳过检查(以获得微小的性能提升),请使用:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-09-23
      • 1970-01-01
      • 2021-06-21
      • 1970-01-01
      • 1970-01-01
      • 2014-03-09
      • 1970-01-01
      相关资源
      最近更新 更多