C++ Finite State Machine

Here is the code I wrote for a FSM in C++. It’s really simple yet useful:

// fsm.h
// #pragma once

#include <iostream>
#include <string>
#include <vector>
#include <map>

class FSM;

typedef void(*state_enter_method_t)(void*, void*);
typedef void(*state_exit_method_t)(void*);
typedef std::vector<std::string> state_vec_t;

class State
{
    public:
        State(const std::string& name, state_enter_method_t enter_method,
              state_exit_method_t exit_method);
        
        void add_transition(const std::string& next_state);
        bool is_next_state_valid(const State* next_state) const;

        std::string get_name() const;
        
        void enter(FSM* fsm, void* arg) const;
        void exit(FSM* fsm) const;
        
    private:
        std::string m_name;
        state_enter_method_t m_enter_method;
        state_exit_method_t m_exit_method;
        state_vec_t m_next_states;
};

typedef std::map<std::string, State*> state_map_t;

class FSM
{
    public:
        FSM(const std::string& name);
        ~FSM();

        void set_FSM_name(const std::string& name);
        std::string get_FSM_name() const;

        void add_state(State* state);
        
        bool is_in_transition();

        State* get_current_state() const;
        State* get_prev_state() const;
        State* get_next_state() const;
        
        State* get_state_named(const std::string& state);

        void request(const std::string& next_state, void* arg=NULL);
        
    private:
        std::string m_name;
        State* m_cur_state;
        State* m_prev_state;
        State* m_next_state;
        state_map_t m_state_map;
};

#define DECL_STATE(CLASS, X) inline static void __enter_##X(void* _this, void* arg) {((CLASS*)_this)->enter_##X(arg);};\
                             inline static void __exit_##X(void* _this) {((CLASS*)_this)->exit_##X();};\
                             void enter_##X(void* arg);void exit_##X();

#define ADD_STATE(CLASS, X) add_state(new State(#X, &##CLASS::__enter_##X, &##CLASS::__exit_##X))
#define ADD_TRANSITION(X, Y) get_state_named(#X)->add_transition(#Y)

#define DEF_ENTER(CLASS, X) void CLASS::enter_##X(void* arg)
#define DEF_EXIT(CLASS, X) void CLASS::exit_##X()

#define EMPTY_ENTER(CLASS, X) void CLASS::enter_##X(void*){};
#define EMPTY_EXIT(CLASS, X) void CLASS::exit_##X(){};

// fsm.cxx
// #include "fsm.h"
#include <algorithm>

State::State(const std::string& name, state_enter_method_t enter_method,
             state_exit_method_t exit_method): m_name(name),
                                               m_enter_method(enter_method),
                                               m_exit_method(exit_method)
{
}
        
void State::add_transition(const std::string& next_state)
{
    m_next_states.push_back(next_state);
}

bool State::is_next_state_valid(const State* next_state) const
{
    return std::find(m_next_states.begin(), m_next_states.end(), next_state->get_name()) != m_next_states.end();
}
        
std::string State::get_name() const
{
    return m_name;
}
        
void State::enter(FSM* fsm, void* arg) const
{
    m_enter_method(fsm, arg);
}
        
void State::exit(FSM* fsm) const
{
    m_exit_method(fsm);
}

FSM::FSM(const std::string& name): m_name(name),
                                   m_cur_state(NULL),
                                   m_prev_state(NULL),
                                   m_next_state(NULL)
{
}

FSM::~FSM()
{
    m_cur_state = NULL;
    m_prev_state = NULL;
    m_next_state = NULL;

    for (state_map_t::iterator it = m_state_map.begin(); it != m_state_map.end();
        ++it) delete it->second;
}

void FSM::set_FSM_name(const std::string& name)
{
   m_name = name;
}
        
std::string FSM::get_FSM_name() const
{
    return m_name;
}

void FSM::add_state(State* state)
{
    m_state_map[state->get_name()] = state;
}
        
bool FSM::is_in_transition()
{
    return (m_prev_state != NULL || m_next_state != NULL);
}

State* FSM::get_current_state() const
{
    return m_cur_state;
};

State* FSM::get_prev_state() const
{
    return m_prev_state;
};

State* FSM::get_next_state() const
{
    return m_next_state;
};
        
State* FSM::get_state_named(const std::string& state)
{
    for (state_map_t::iterator it = m_state_map.begin(); it != m_state_map.end(); ++it)
    {
        State* s = it->second;
        if (s->get_name() == state)
            return s;
    }
    
    return NULL;
}
        
void FSM::request(const std::string& next_state, void* arg)
{
    if (is_in_transition())
    {
        std::cerr << "already in transition!!" << std::endl;
        return;
    }
            
    m_next_state = get_state_named(next_state);
    if (!m_next_state)
    {
        std::cerr << "state undefined: " << next_state << std::endl;
        return;
    }
            
    if (m_cur_state && !m_cur_state->is_next_state_valid(m_next_state))
    {
       m_next_state = NULL;
       std::cerr << "no transition from " << m_cur_state->get_name() << " to " << next_state << std::endl;
       return;
    }
            
    m_prev_state = m_cur_state;
    m_cur_state = NULL;
            
    if (m_prev_state)
       m_prev_state->exit(this);
     
    m_next_state->enter(this, arg);
     
    m_cur_state = m_next_state;
    m_prev_state = NULL;
    m_next_state = NULL;
}

// test.cxx
// include "fsm.h"
class TestFSM : public FSM
{
    public:
        TestFSM(): FSM("TestFSM")
        {
            ADD_STATE(TestFSM, state1);
            ADD_STATE(TestFSM, state2);
            ADD_STATE(TestFSM, state3);
            
            ADD_TRANSITION(state1, state2);
            ADD_TRANSITION(state2, state3);
            ADD_TRANSITION(state3, state1);
            ADD_TRANSITION(state3, state2);
        }
        
    private:
        DECL_STATE(TestFSM, state1);
        DECL_STATE(TestFSM, state2);
        DECL_STATE(TestFSM, state3);
};

DEF_ENTER(TestFSM, state1)
{
    std::string mystr = *((std::string*)arg);
    std::cout << "enter state1: " << mystr << std::endl;
}

EMPTY_EXIT(TestFSM, state1);

DEF_ENTER(TestFSM, state2)
{
    int myvalue = *((int*)arg);
    std::cout << "enter state2: " << myvalue << std::endl;
}

EMPTY_EXIT(TestFSM, state2)

DEF_ENTER(TestFSM, state3)
{
    std::cout << "enter state3: this state doesn't use the arg, which is probably NULL ("
              << arg << ")" << std::endl;
}

DEF_EXIT(TestFSM, state3)
{
    std::cout << "exit state3" << std::endl;
}

int main(int, char**)
{
    TestFSM x;
    
    std::string hello("Hello world!");
    x.request("state1", &hello);
    
    int test = 123;
    int test2 = 321;
    x.request("state2", &test);
    
    x.request("state1"); // INVALID, therefore:
    std::cout << "last transition was invalid so, current state is (" << x.get_current_state()->get_name()
              << ") (expected: state2)" << std::endl;
    
    x.request("state3");
    x.request("state2", &test2);
}

Output:

enter state1: Hello world!
enter state2: 123
no transition from state2 to state1
last transition was invalid so, current state is (state2) (expected: state2)
enter state3: this state doesn't use the arg, which is probably NULL (00000000)
exit state3
enter state2: 321

Basic usage:

  • Subclass FSM.

  • In the class declaration, use DECL_STATE(classname, statename).

  • In your constructor, use ADD_STATE and ADD_TRANSITION.

  • To define the state enter/exit handler, use

DEF_ENTER(classname, statename) {...}

or

DEF_EXIT(classname, statename) {...}
  • Within ENTER methods, you have access to arg, which is a void* of the arg passed in request. Cast it to whatever you want. Be careful, it might be NULL.

  • If your state doesn’t need enter or exit methods, you can use

EMPTY_ENTER(classname, statename)

or

EMPTY_EXIT((classname, statename)

which creates a dummy entry for you.

Enjoy it!

2 Likes