I finally found a good way to implement a "stack-of-states" to manage what screen I am on. Minimal example:

    class gameEngine {
    //Removes state at the "back" of the stack and puts the new state at the "back" of the stack
    void ChangeState(std::shared_ptr<gameState> state);

    //Adds new state to top of stack
    void PushState(std::shared_ptr<gameState> state);

    //Removes back state from stack
    void PopState();

    // The stack of states
    std::vector<gameState*> stateStack;

Now how I call the (for example) PushState command from inside a state itself (using tGUI library), where "stateGame" and "stateMainMenu" are game states:

void stateMainMenu::Init(gameEngine* game) {

    //Creates a new button
    tgui::Button::Ptr buttonStart = std::make_shared<tgui::Button>();

    //Connects the PushState function to this button. When clicked the engine will run the
    //PushState function with the arguments 'game' and a pointer to a new StateGame.
    buttonStart->connect("clicked", &gameEngine::PushState, game, std::make_shared<stateGame>());

This works, and I can transition between screens easily. However, I had a strange feeling that I shouldn't allow gameStates to know about the internals of other game states (I am including the header file for each gameState into each cpp file where I will need to transition to that state). Is it better to re-write the ChangeState and PushState functions to take an enum which refers to each game state, and then generate the shared pointers inside those functions? I'm really scratching my head, it probably makes no difference at all but I want to know what the better practice would be.

If anyone can make any sense of my rambling and put it to rest I would be extremely grateful.

Here is the full source for anyone interested, or who wants more context.

  • \$\begingroup\$ I stripped the bit of the question about shared_ptr usage and std::move, as it's more of a general programming topic (suitable for SO) and because posts should generally focus on one question or topic at a time. \$\endgroup\$
    – user1430
    Mar 11 '16 at 17:29

You can't really avoid the problem of source state knowing something about the target state -- at the bare minimum the source state must know some unique identifier or name related to the target state to indicate which target state to go to.

In that regard an enumeration isn't really any different than a type name, and in fact might be worse from a maintenance perspective. With such an enumeration, creating a new kind of state involves

  • writing the state's class
  • adding an entry to the enumeration
  • adding an entry somewhere that establishes the mapping between enumeration value and state type

So you've moved the "problem" of knowing the state's type from one place (the source state code) to some other place (some random spot where you have to write the enumeration-to-type mapping), and you've significantly increased the parts of code you have to touch to add a new state type into the mix. Compare to your current approach, which really requires only

  • writing the state's class

I'd say your current approach is the better one. Additionally remember that exposing the name of the type is not the same as exposing it's "internals." The name of a type is part of its public interface.

Further, the enumeration approach basically requires that you include all possible state types in your code. That is, if you turn your code into a library at some point, you can't easily have users of that library create new state types because C++ enumerations aren't extensible. So you'll have to jump through more hoops to create a sort of ID registration system for states.

  • \$\begingroup\$ Yeah sorry, new to stackexchange. Trying to add code to my comment isn't working. github.com/DanielCordell/SFML-Game-Test/blob/master/Game/Game/… This was the header file that I'm including. The comment you made seems to imply that just exposing the name of a type is fine, but I think I'm doing more than just that. \$\endgroup\$ Mar 11 '16 at 17:32
  • \$\begingroup\$ @DanielCordell Including the header for the state is fine; that's how C++ works. That's not "exposing" the internals of the class so long as your class makes appropriate use of the public: and private: access specifiers to hide things that aren't appropriate for clients of its API to use. It's better to forward-declare when you can, because it helps reduce compilation times when headers change, but in this case make_shared is going to need the definition of the type to invoke the constructor so you'd have to include the full header, and that's OK. \$\endgroup\$
    – user1430
    Mar 11 '16 at 17:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.