1

I'm continuously running into the same problem, and can't fix it even when looking through tutorials. I've "set up" my State machine, but I can't transition between states.

Here is my StateMachine:

class StateMachine
{
    State* m_State;

public:
    StateMachine();
    ~StateMachine();
    void changeState(State* state);
};

And here is an example State:

class A : State
{
public:
    A();
    ~A();
    void handleInput(int a);
}

If I pass a = 1 into A::handleInput() I want to transition to State B. But when I implement it I can't access the StateMachine from A::handleInput(), making me scrub my head in agony.

Okami1301
  • 9
  • 2
  • That's a common problem with the [_State Pattern_](https://sourcemaking.com/design_patterns/state). Fortunately a forward reference is enough for that case, and access to the `StateMachine` class of course, – πάντα ῥεῖ Oct 27 '20 at 21:32
  • Unrelated side note: If you really need those destructors make sure [you don't also need its friends](https://en.cppreference.com/w/cpp/language/rule_of_three). – user4581301 Oct 27 '20 at 21:35
  • IMHO, A *state machine* **contains** one or more states. Thus there should be a container, not a pointer, of the states. A state may want to have a *transition* method, that **returns** the *next* state based on the given input. – Thomas Matthews Oct 27 '20 at 21:38
  • 2
    _@Okami_ You may be interested in this work I did some years ago: [_State machine template class framework for C++_](https://makulik.github.io/sttcl/) It's a bit complicated, but well documented, and hopefully easy to use. – πάντα ῥεῖ Oct 27 '20 at 21:40
  • Surprisingly few Greek characters in what I've looked at in that code... – user4581301 Oct 27 '20 at 22:11
  • @user Sure, Δεν είμαι Έλληνας :) – πάντα ῥεῖ Oct 27 '20 at 22:46

2 Answers2

0

But when I implement it I can't access the StateMachine from A::handleInput()

Well, that's a well known problem with the State Pattern, that there's no mention how to keep the state classes in track with an enclosing State Machine.

IMO, that's one of the valid use cases to consider the StateMachine class as being implemented as a Singleton.
This way it's instance would be accessible from any Stateclass implementation.

As I'm talking in terms of Design Patterns here, the State classes could be designed with help of the Flyweight Pattern, since they're usually stateless themselves.

I've once driven all that into a c++ template framework, which abstracts the interfaces of State and State Machine (see link below).

Here's a short code example by these means:

StateMachine.h

struct State {
    virtual void handleInput(int x) = 0;
    virtual ~State() {} = 0;
};

class StateMachine {
    State* m_State;

    StateMachine();
public:
   
    static StateMachine& instance() {
        static StateMachine theInstance;
        return theInstance;
    }
    void changeState(State* state) {
        m_State = state;
    }
    void triggerInput(int x) {
        m_State->handleInput(x);
    }
};

StateA.h

#include "StateMachine.h"
class StateB;
extern StateB* stateB;

class StateA : public State {
public:
    virtual ~StateA() {}
    virtual void handleInput(int x) {
        if(x == 1) {
             // Change to StateB
             StateMachine::instance.changeState(stateB);
        }
        else {
            // Do something with x
        }
    }
};

I omit the definition od StateB here, should be the same manner as StateA.


References:

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
0

I've taken a look at the Sourcemaking example and for me the implementation example really sucks; having to create new instances upon every state change: https://sourcemaking.com/design_patterns/state/cpp/1

Personally as someone who's designed state machines in electronics with JK flip flops, I would use a similar but semantically different approach. The complexity in state machines involves the action performed according to the state and input; typically in C you would do this with lots of switch statements and possibly arrays describing how to handle the current state and new input aka event.

So to me the OO approach to this would be to model the event handler. This would have an interface which describes the format of the inputs. You then have different implementations of that interface for each different state. With that, the state machine can simply implement a collection of states to event handlers - array, vector or map. Although the handlers still may contain case statements, the overall spaghettiness is very much reduced. You can easily extend the design with new state handlers as and when necessary:

So you could have something like this:


#include <map>

typedef enum
{
    //TODO : state list, e.g.
    eOff,
    eOn
}
teCurrentState;

typedef struct
{
    //TODO : Add inputs here, e.g.
    bool switch1;
}
tsInputDesc;

typedef struct
{
    //TODO : Add outputs here, e.g.
    bool relay1;
}
tsOutputDesc;

// ------------------------------------------------

class IEventHandler
{
public:
    virtual ~IEventHandler() {}
    // returns new state
    virtual teCurrentState handleInput(tsInputDesc const& input, tsOutputDesc& output) = 0;
};

// ------------------------------------------------

class OnStateHandler : public IEventHandler
{
public:
    virtual teCurrentState handleInput(tsInputDesc const& input, tsOutputDesc& output) override
    {
        //TODO : IMPLEMENT
        teCurrentState newState = TODO....
        return (newState);
    }
};

// ------------------------------------------------

class OffStateHandler : public IEventHandler
{
public:
    virtual teCurrentState handleInput(tsInputDesc const& input, tsOutputDesc& output) override
    {
        //TODO : IMPLEMENT
        teCurrentState newState = TODO....
        return (newState);
    }
};

// ------------------------------------------------

class StateMachine
{
protected:
    teCurrentState mCurrentState;
    std::map<teCurrentState, IEventHandler*> mStateHandlers;

    void makeHandlers()
    {
        mStateHandlers[eOff] = new OffStateHandler();
        mStateHandlers[eOn] = new OnStateHandler();
    }

public:
    StateMachine()
    {
        makeHandlers();
        mCurrentState = eOff;
    }

    void handleInput(tsInputDesc const& input, tsOutputDesc output)
    {
        teCurrentState newState = mStateHandlers[mCurrentState]->handleInput(input, output);
        mCurrentState = newState;
    }
};

// ------------------------------------------------

void runFsm()
{
    StateMachine fsm;
    tsInputDesc input;
    tsOutputDesc output;
    bool alive = true;

    while (alive)
    {
        // TODO : set input according to....inputs (e.g. read I/O port etc)

        fsm.handleInput(input, output);

        // TODO : use output
    }
}
Den-Jason
  • 2,395
  • 1
  • 22
  • 17