読者です 読者をやめる 読者になる 読者になる

Boost.statechart で現在の状態を確認する。

C++

Boost.statechart の state_machine には、現在の状態を確認する方法が用意されています。当たり前ですね。無かったらキレますよね。
説明を簡単にするためにこんな例でコードに落としてみます。

シンプルですね。遷移すらしない状態マシンです。parent 状態の内部に child 状態を定義しています。

#include <iostream>
#include <boost/statechart/state_machine.hpp>
#include <boost/statechart/simple_state.hpp>
#include <boost/statechart/event.hpp>
#include <boost/statechart/transition.hpp>

using namespace std;

namespace sc = boost::statechart;

namespace states
{
	struct parent;
	struct child;
}

// マシンの定義
struct machine : sc::state_machine< machine, states::parent >
{
	machine() { initiate(); }
};

// 状態の定義
namespace states
{
	struct parent : sc::simple_state< parent, machine, child > {};
	struct  child : sc::simple_state< child, parent > {};
}

int main()
{
	// parent :: child で初期化。
	machine the_machine;

	// 現在の状態を表示
	for( machine::state_iterator i = the_machine.state_begin(); i != the_machine.state_end(); ++ i ) {
		if( typeid( *i ) == typeid( states::parent ) ) { cout << "parent" << endl; }
		if( typeid( *i ) == typeid( states::child ) )  { cout << "child" << endl; }
	}
}

 ちょっと待って何ですかその最後のループは?


 解説。

 まずループの前に、カレント状態を確認する機構があったとして、それを「どうやって表現するか」に注目します。そもそも各状態に std::string で状態の名称を指定したわけでもなく、ID をふったわけでも無いので表現のしようがありませんよね。唯一違うのは状態の型です。型のみがそれぞれの状態を identify し得るようになっています。従って typeid で識別するようになっているわけです。

 で、ループですが。ループしているということからも予測できる通り、state_machine は、「現在の状態」を複数持つことができます。先のプログラムの出力を見てみます。

child
……これだけです。「parent」とは表示されません。現在の状態は「parent :: child」なのに parent はループしても取り出せないのです。何故でしょうか?実はその理由は child の定義を見ればわかります。child は、sc::simple_state< child, parent > と定義されています。つまり "child は、parent に属する状態である" と定義されています。すなわち、現在の状態が child であればその親は parent だと分かり切っているからわざわざ machine がそれを明示する必要は無いということです。

 では、「現在の状態を複数持つ」とはどういうことか?

 図にするとこういうことです。

machine は、常に ON か OFF のいずれかの状態であり、
   かつ、常に enable か disable かどちらかの状態であり、
   かつ、常に mode1 か mode2 か mode3 のいずれかの状態である。

ということです。
こういう表現をコードで実装する……のは面倒になったのでこの記事はここで締めます。気が向いたらサンプルコードを載せますが気が向かなかったら載せません。