有限状態機械(Finite State Machine:FSM)の実装

マルチスレッドなアプリケーションの状態遷移が楽になるかも?
ということで、ステートマシンを使ってみます。


状態遷移図

ツールを使って以下のような状態遷移図が書けるとちょっとだけ幸せになれるかもしれません。

stop watch


階層型有限状態機械(Hierarchical Finite State Machine)

今回の目的はHFSMを実装することです。(上の図のようなネストされた状態遷移)
boostにもステートマシンクラスが用意されていますが(boost::statechart::state_machine)
いきなり使うのはちょっとハードルが高そうだったので、Stateパターンを使った単純なものから実装していきたいと思います。

Stateパターン

stateインターフェイス
  • entry
    • 入場動作。状態が開始される時
  • exe
    • 実行活動。状態継続中
  • exit
    • 退場動作。状態が終了する時
template < typename ContextType >
class state_interface
{
public:
    virtual void entry( ContextType* context ) = 0;
    virtual void exe( ContextType* context ) = 0;
    virtual void exit( ContextType* context ) = 0;
};
state_machineクラス
  • CRTP
template < typename ContextType >
class state_machine
{
protected:
    typedef ContextType context_t;
    typedef state_interface< ContextType > state_t;

private:
    //開始状態
    struct start
        : state_interface< context_t >
        , singleton< start >
    {
        void entry( context_t* /*context*/ ){}
        void exe( context_t* /*context*/ ){}
        void exit( context_t* /*context*/ ){}
    };

    //終了状態
    struct end
        : state_interface< context_t >
        , singleton< end >
    {
        void entry( context_t* /*context*/ ){}
        void exe( context_t* /*context*/ ){}
        void exit( context_t* /*context*/ ){}
    };

private:
    state_t* current;

public:
    state_machine()
        : current( start::get_instance() )
    {}

    virtual ~state_machine()
    {
        change_state( end::get_instance() );
    }

public:
    void update()
    {
        current->exe( derived() );
    }

    void change_state( state_t* new_state )
    {
        current->exit( derived() );
        current = new_state;
        current->entry( derived() );
    }

    ContextType* derived()
    {
        return static_cast< ContextType* >( this );
    }
};
singletonクラス

各状態はsingletonクラスを継承します。

template < typename T >
class singleton
{
protected:
    singleton(){}
    singleton( singleton const& );
    ~singleton(){}

    singleton& operator=( singleton const& );

public:
    static T* get_instance()
    {
        static T instance;
        return &instance;
    }
};

Hello World


struct hoge
    : state_machine< hoge >
{
    struct greeting
        : state_interface< hoge >
        , singleton< greeting >
    {
        void entry( context_t* /*context*/ );
        void exe( context_t* /*context*/ ){}
        void exit( context_t* /*context*/ );
    };

public:
    hoge();
};

hoge::hoge()
{
    change_state( hoge::greeting::get_instance() );
}

void hoge::greeting::entry( context_t* /*context*/ )
{
    std::cout << _T( "hello world" );
}

void hoge::greeting::exit( context_t* /*context*/ )
{
    std::cout << _T( "bye bye world" );
}

int main()
{
    hoge h;
}
実行結果
hello world
bye bye world

雑感

  • ステートマシンと関係ないが、singletonが気に入らない。派生クラスのコンストラクタをpublicにすると、派生クラスのインスタンスが作れてしまう。privateにするとfriendの指定をしないとget_instanceにアクセスできなくなってしまう。
  • state_interfaceのentry、exe、exitの実装の強制は面倒かも
  • 次回は状態の階層化