Skip to main content
3 of 20
added 94 characters in body
nilo
  • 791
  • 2
  • 6
  • 15

Better C++ FSM solution?

I have been looking for a good hierarchical FSM (finite state machine) solution in C++ for a while. I have always been put off by what I have found:

  • Hard to get an overview of the state machine (e.g. switch statement based)
  • Too much pre-processor magic (which I prefer to avoid, if possible)
  • Need for an FSM compiler (I prefer to keep the whole code in one environment, and I don't believe much in a graphical solution)
  • Need for an external library (which I prefer to avoid, if possible)
  • Too cumbersome

On the opposite, my criteria for a good solution are:

  • Simple
  • As standard C++ as possible
  • Allows an easy overview of the FSM's structure
  • Support for all FSM features as defined, for instance, by the UML

I have experimented on an elaboration of the state pattern, based on a class template.

I can summarize my compromise as follows:

  • I do not use any macro.
  • I only use standard C++ (compiled with a fairly recent g++, with C++11 flag off).
  • I use a single state template (quite a short one), which allows quite a clean an intuitive syntax for state transitions.
  • The template has support for entry and exit actions, but I have chosen simplicity over UML compliance: the exit action of the current state and the entry action of the new state are run in the state transition method, which is supposed to be run after the transition actions (I write "supposed" because my implementation does not enforce that - I do not seek to restrain possibilities just for the sake of it). Note that this order of actions is the same as in Miro Samek's framework (a guy who sells so many books about state machines can't be entirely wrong ;-))
  • It is possible to implement a hierarchical state machine with the template as is, by instantiating state members in the concrete states. They are however not fully UML compliant, as the exit actions of the nested states will not be executed for transitions between higher states. The entry actions are always executed, but apart from that, the nested states are quite isolated from the higher states. I believe that practical impact is fairly manageable.
  • One drawback I think I have to mention is that the declaration of states is too cumbersome for my taste. I have however chosen not to implement pre-processor support, because it worsens my editor's support for effective and interactive code edition. With good code completion, the situation is not too bad. But Bjarne should really explicitly build FSM support in C++ :-).
  • Orthogonal regions are supported. One orthogonal region is an abstract state that is an instantiation of the template. A state machine, or a composite state, can have several.
  • Guards are supported in a trivial manner. One just uses if or switch in an event handler.

Since talk is cheap, here comes the code (public domain, use it as you wish, but don't blame me), that demonstrates all of these features.

  • genericstate.h: the template, the rest of the files are just a demo.
  • motorcontroller.h and .cpp: a class that implements an arbitrary state machine. It has two state variables that represent arbitrary orthogonal regions (running state and direction state), that each instantiates its class.
  • main.cpp: instantiate the state machine and send to it an arbitrary sequence of events.

All in all, I am quite impressed by what can be done with a 40-line class template in the form of support for hierarchical FSM. A short single template is I believe what characterizes my solution compared to all the others I have read about.

I am interested by any critical feedback, especially about bugs, caveats, or links to more tested similar solutions (that I would have missed).

The program outputs the following:

entering Off
entering Left
entering Up
going down
leaving up
entering down
already down
already left
turning right
leaving Left
entering Right
unhandled event
unhandled event
unhandled event
sending motor start command
leaving Off
entering WaitingForMotorStart
unhandled event
unhandled event
turning left
leaving Right
entering Left
entering Up
motor start timeout, will retry to start it
leaving WaitingForMotorStart
entering WaitingForMotorStart
going down
leaving up
entering down
turning right
leaving Left
entering Right
unhandled event
motor start timeout, will retry to start it
leaving WaitingForMotorStart
entering WaitingForMotorStart
motor has started
leaving WaitingForMotorStart
entering Running
unhandled event
motor started out of context, cutting power
leaving Running
entering Off
unhandled event
motor stopped out of context, cutting power
leaving Off
entering Off
sending motor start command
leaving Off
entering WaitingForMotorStart
motor has started
leaving WaitingForMotorStart
entering Running
emergency, cutting power
leaving Running
entering Off

genericstate.h

#ifndef GENERICSTATE_H
#define GENERICSTATE_H

template <typename StateMachine, class SpecificState>
class GenericState
{
public:
    virtual ~GenericState() {}
    virtual void entry() {}
    virtual void exit() {}
    virtual void init() {}

    template <class ConcreteState>
    static void init(StateMachine &m, SpecificState *&state) {
        state = new ConcreteState(m, state);
        state->entry();
        state->init();
    }

    template <class ConcreteState>
    void change() {
        exit();
        init<ConcreteState>(m, state);
        delete this;
    }

    static void destroy(SpecificState *&state) {
        delete state;
        state = 0;
    }

protected:
    explicit GenericState(StateMachine &m, SpecificState *&state) :
        m(m), state(state) {}

protected:
    StateMachine &m;

private:
    SpecificState *&state;
};

#endif // GENERICSTATE_H

motorcontroller.h

#ifndef MOTORCONTROLLER_H
#define MOTORCONTROLLER_H

#include "genericstate.h"

class MotorController
{
public:
    MotorController();
    ~MotorController();
    void start();
    void switchOn() { runningState->switchOn(); }
    void switchOff() { runningState->switchOff(); }
    void motorStarted() { runningState->motorStarted(); }
    void motorStopped() { runningState->motorStopped(); }
    void emergencyStop() { runningState->emergencyStop(); }
    void motorTimeout() { runningState->motorTimeout(); }
    void turnRight() { directionState->turnRight(); }
    void turnLeft() { directionState->turnLeft(); }
    void goUp() { directionState->goUp(); }
    void goDown() { directionState->goDown(); }

private:
    static void print(const char* string);
    static void unhandledEvent();
    void startMotor();
    void stopMotor();

private:
    class RunningState : public GenericState<MotorController, RunningState> {
    public:
        explicit RunningState(MotorController &m, RunningState *&state) :
            GenericState<MotorController, RunningState>(m, state) {}
        virtual void switchOn();
        virtual void switchOff();
        virtual void motorStarted();
        virtual void motorStopped();
        virtual void emergencyStop();
        virtual void motorTimeout();
    } *runningState;

    class Off : public RunningState {
    public:
        explicit Off(MotorController &m, RunningState *&state) :
            RunningState(m, state) {}
        void entry();
        void switchOn();
        void exit();
    };

    class WaitingForMotorStart : public RunningState {
    public:
        explicit WaitingForMotorStart(MotorController &m,
                                      RunningState *&state) :
            RunningState(m, state) {}
        void entry();
        void switchOn();
        void switchOff();
        void motorStarted();
        void motorTimeout();
        void exit();
    };

    class Running : public RunningState {
    public:
        explicit Running(MotorController &m, RunningState *&state) :
            RunningState(m, state) {}
        void entry();
        void switchOff();
        void exit();
    };

    class WaitingForMotorStop : public RunningState {
    public:
        explicit WaitingForMotorStop(MotorController &m,
                                      RunningState *&state) :
            RunningState(m, state) {}
        void entry();
        void switchOn();
        void switchOff();
        void motorStarted();
        void motorTimeout();
        void exit();
    };

    class DirectionState :
            public GenericState<MotorController, DirectionState> {
    public:
        explicit DirectionState(MotorController &m, DirectionState *&state) :
            GenericState<MotorController, DirectionState>(m, state) {}
        virtual ~DirectionState() {}
        virtual void turnRight() = 0;
        virtual void turnLeft() = 0;
        virtual void goUp() { unhandledEvent(); }
        virtual void goDown() { unhandledEvent(); }
    } *directionState;

    class LeftState : public GenericState<MotorController, LeftState> {
    public:
        explicit LeftState(MotorController &m, LeftState *&state) :
            GenericState<MotorController, LeftState>(m, state) {}
        virtual void goUp() = 0;
        virtual void goDown() = 0;
    };

    class Up : public LeftState {
    public:
        explicit Up(MotorController &m, LeftState *&state) :
            LeftState(m, state) {}
        void entry();
        void goUp();
        void goDown();
        void exit();
    };

    class Down : public LeftState {
    public:
        explicit Down(MotorController &m, LeftState *&state) :
            LeftState(m, state) {}
        void entry();
        void goUp();
        void goDown();
        void exit();
    };

    class Left : public DirectionState {
    public:
        explicit Left(MotorController &m, DirectionState *&state) :
            DirectionState(m, state) {}
        ~Left() { LeftState::destroy(state); }
        void entry();
        void init();
        void turnRight();
        void turnLeft();
        void goUp() { state->goUp(); }
        void goDown() { state->goDown(); }
        void exit();
    private:
        LeftState *state;
    };

    class Right : public DirectionState {
    public:
        explicit Right(MotorController &m, DirectionState *&state) :
            DirectionState(m, state) {}
        void entry();
        void turnRight();
        void turnLeft();
        void exit();
    };
};

#endif // MOTORCONTROLLER_H

motorcontroller.cpp

#include <iostream>

#include "motorcontroller.h"

MotorController::MotorController() :
    runningState(0), directionState(0)
{
}

MotorController::~MotorController()
{
    RunningState::destroy(runningState);
    DirectionState::destroy(directionState);
}

void MotorController::start()
{
    RunningState::init<Off>(*this, runningState);
    DirectionState::init<Left>(*this, directionState);
}

void MotorController::print(const char *string)
{
    std::cout << string << std::endl;
}

void MotorController::unhandledEvent()
{
    print("unhandled event");
}

void MotorController::startMotor()
{
    print("sending motor start command");
}

void MotorController::stopMotor()
{
    print("sending motor stop command");
}

void MotorController::RunningState::switchOn()
{
    unhandledEvent();
}

void MotorController::RunningState::switchOff()
{
    unhandledEvent();
}

void MotorController::RunningState::motorStarted()
{
    print("motor started out of context, cutting power");
    change<Off>();
}

void MotorController::RunningState::motorStopped()
{
    print("motor stopped out of context, cutting power");
    change<Off>();
}

void MotorController::RunningState::emergencyStop()
{
    print("emergency, cutting power");
    change<Off>();
}

void MotorController::RunningState::motorTimeout()
{
    unhandledEvent();
}

void MotorController::Off::entry()
{
    print("entering Off");
}

void MotorController::Off::switchOn()
{
    m.startMotor();
    change<WaitingForMotorStart>();
}

void MotorController::Off::exit()
{
    print("leaving Off");
}

void MotorController::WaitingForMotorStart::entry()
{
    print("entering WaitingForMotorStart");
}

void MotorController::WaitingForMotorStart::switchOn()
{
    print("already waiting for the motor to start, will do nothing");
}

void MotorController::WaitingForMotorStart::switchOff()
{
    print("can't stop the motor while waiting for start, use emergency stop");
}

void MotorController::WaitingForMotorStart::motorStarted()
{
    print("motor has started");
    change<Running>();
}

void MotorController::WaitingForMotorStart::motorTimeout()
{
    print("motor start timeout, will retry to start it");
    change<WaitingForMotorStart>();
}

void MotorController::WaitingForMotorStart::exit()
{
    print("leaving WaitingForMotorStart");
}

void MotorController::Running::entry()
{
    print("entering Running");
}

void MotorController::Running::switchOff()
{
    m.stopMotor();
    change<WaitingForMotorStop>();
}

void MotorController::Running::exit()
{
    print("leaving Running");
}

void MotorController::WaitingForMotorStop::entry()
{
    print("entering WaitingForMotorStop");
}

void MotorController::WaitingForMotorStop::switchOn()
{
    print("waiting for the motor to stop, will do nothing");
}

void MotorController::WaitingForMotorStop::switchOff()
{
    print("waiting for the motor to stop, will do nothing");
}

void MotorController::WaitingForMotorStop::motorStarted()
{
    print("starting while stopping, will cut power");
    change<Off>();
}

void MotorController::WaitingForMotorStop::motorTimeout()
{
    print("motor stop timeout, will cut power");
    change<Off>();
}

void MotorController::WaitingForMotorStop::exit()
{
    print("leaving WaitingForMotorStop");
}


void MotorController::Up::entry()
{
    print("entering Up");
}

void MotorController::Up::goUp()
{
    print("already up");
}

void MotorController::Up::goDown()
{
    print("going down");
    change<Down>();
}

void MotorController::Up::exit()
{
    print("leaving up");
}

void MotorController::Down::entry()
{
    print("entering down");
}

void MotorController::Down::goUp()
{
    print("going up");
    change<Up>();
}

void MotorController::Down::goDown()
{
    print("already down");
}

void MotorController::Down::exit()
{
    print("leaving Down");
}

void MotorController::Left::entry()
{
    print("entering Left");
}

void MotorController::Left::init()
{
    LeftState::init<Up>(m, state);
}

void MotorController::Left::turnRight()
{
    print("turning right");
    change<Right>();
}

void MotorController::Left::turnLeft()
{
    print("already left");
}

void MotorController::Left::exit()
{
    print("leaving Left");
}

void MotorController::Right::entry()
{
    print("entering Right");
}

void MotorController::Right::turnRight()
{
    print("already right");
}

void MotorController::Right::turnLeft()
{
    print("turning left");
    change<Left>();
}

void MotorController::Right::exit()
{
    print("leaving Right");
}

main.cpp

#include "motorcontroller.h"

int main()
{
    MotorController mc;
    mc.start();

    mc.goDown();
    mc.goDown();
    mc.turnLeft();
    mc.turnRight();
    mc.goDown();
    mc.switchOff();
    mc.goUp();
    mc.switchOn();
    mc.goDown();
    mc.goUp();
    mc.turnLeft();
    mc.motorTimeout();
    mc.goDown();
    mc.turnRight();
    mc.goUp();
    mc.motorTimeout();
    mc.motorStarted();
    mc.goUp();
    mc.motorStarted();
    mc.goDown();
    mc.motorStopped();
    mc.switchOn();
    mc.motorStarted();
    mc.emergencyStop();

    return 0;
}
nilo
  • 791
  • 2
  • 6
  • 15