vaati
vaati

Reputation: 162

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:

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:

  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:

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

The only way I can access the class Game in MainMenu is by casting the pointer?

// 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.

Upvotes: 2

Views: 2935

Answers (2)

Christophe
Christophe

Reputation: 73366

The answer of immibis is perfect for addressing the technical casting issue.

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. So there is no need to have the same signature for update() and draw(). Do you ever foresee to have one of these StateManager functions called with another StateManager in argument ? For a Game, I think this makes no sense. So I'd recommend to refactor the class (and adapt Game accordingly):

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). 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:

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.
  • Concerete implementations of GameState, which rely on Game, can use it directly, but should use it only for Game specific abstractions.

The main inconveniences are:

  • GameState must always be created for one specifc StateManager.
  • The robustness of the design comes at the cost of a redundant pointer in all GameState implementations such as MainMenu . If you have millions of them, it could become a memory issue.

Upvotes: 3

If you're not sure whether stateManager points to a Game, then use:

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.

If you are sure it's always a Game and want to skip the check (for a tiny performance gain), use:

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

which will produce undefined behaviour if stateManager doesn't point to a Game.

Upvotes: 1

Related Questions