简体   繁体   English

从抽象基类指针到派生类的转换

[英]Casting from abstract base class pointer to derived class

I'm trying to create a state manager for my game, and I have 4 classes: 我正在尝试为我的游戏创建一个状态管理器,我有4个类:

GameState: 游戏状态:

  // 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: 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_;
  };

Game: 游戏:

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

And MainMenu: 和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) {}
}

When I initialize my game like so: game.addState(new MainMenu()) . 当我这样初始化游戏时: game.addState(new MainMenu())

The only way I can access the class Game in MainMenu is by casting the pointer? 我可以在MainMenu访问类Game的唯一方法是通过投射指针?

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

Is this right? 这是正确的吗? Something tells me I'm doing it wrong. 某事告诉我我做错了。

The answer of immibis is perfect for addressing the technical casting issue. immibis的答案非常适合解决技术铸造问题。

Nevertheless there's something weird in your design. 但是,您的设计中有些奇怪。 So I'd like to provide an alternative answer, addressing the design issues. 因此,我想提供一个替代答案,以解决设计问题。

First a StateManager is not itself a GameState . 首先, StateManager本身不是GameState So there is no need to have the same signature for update() and draw() . 因此,对于update()draw()不需要具有相同的签名。 Do you ever foresee to have one of these StateManager functions called with another StateManager in argument ? 您是否曾预见过要在参数中用另一个StateManager调用这些StateManager函数之一? For a Game , I think this makes no sense. 对于Game ,我认为这没有任何意义。 So I'd recommend to refactor the class (and adapt Game accordingly): 因此,我建议重构类(并相应地修改Game ):

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

Next, it seems that the StateManager owns the GameState (the protected vector using a unique_ptr<> ,and your casting question suggest it). 接下来,似乎StateManager拥有GameState (使用unique_ptr<>的受保护矢量,并且您的强制转换问题建议了它)。 So another design could be interesting as well: 因此,另一种设计可能也很有趣:

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

Following this logic, the MainMenu would be refactored as: 按照此逻辑, 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.  
};  

The advantages of this alternative design are: 这种替代设计的优点是:

  • the code will be much lighter, and less error prone, as you don't always to worry about the member function's argument. 因为您不必总是担心成员函数的参数,所以代码将更加轻巧,并且不易出错。
  • StateManager pointer is used only for the StateManager abstraction. StateManager指针仅用于StateManager抽象。
  • Concerete implementations of GameState , which rely on Game , can use it directly, but should use it only for Game specific abstractions. 依赖于GameGameState实现可以直接使用它,但仅应将其用于特定于游戏的抽象。

The main inconveniences are: 主要的不便是:

  • GameState must always be created for one specifc StateManager . 必须始终为一个特定的StateManager创建GameState
  • The robustness of the design comes at the cost of a redundant pointer in all GameState implementations such as MainMenu . 设计的健壮性是以所有GameState实现(例如MainMenu中的冗余指针为代价的。 If you have millions of them, it could become a memory issue. 如果您有数百万个,则可能会成为内存问题。

If you're not sure whether stateManager points to a Game , then use: 如果不确定stateManager是否指向Game ,请使用:

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

game will then contain a null pointer if stateManager did not point to a Game , otherwise it will contain a pointer to the game. 如果stateManager没有指向Game ,则game将包含一个空指针,否则它将包含一个指向游戏的指针。

If you are sure it's always a Game and want to skip the check (for a tiny performance gain), use: 如果您确信总是一个Game ,想跳过检查(一个微小的性能增益),使用方法:

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

which will produce undefined behaviour if stateManager doesn't point to a Game . 如果stateManager不指向Game ,它将产生不确定的行为。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM