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

Boost.statechart で状態のネストを定義する。

C++

 Boost.statechart では、ネストした状態の定義が可能です。例としてこんなんを考えてみます。

 状態マシンは power_on と power_off の 2 状態を持ちます。power_on のときはその内側でさらに bit_0 と bit_1 の 2 状態を持ちます。それぞれ push_power_button, push_flip_button イベントでトグルします。これをコードにするとこんな感じになります。

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

using namespace std;
namespace sc = boost::statechart;

namespace states
{
	struct power_on;
		struct bit_0;
		struct bit_1;
	
	struct power_off;
}

// 状態マシン。power_off で初期化。
struct machine : sc::state_machine< machine, states::power_off >
{
	machine() { initiate(); }
};

// イベントの定義
namespace events
{
	struct push_power_button : sc::event< push_power_button > {};
	
	struct push_flip_button : sc::event< push_flip_button > {};
}

// 状態の定義
namespace states
{
	// power_on は machine の状態のひとつ。内部は bit_0 が初期状態。
	struct power_on : sc::simple_state< power_on, machine, bit_0 >
	{
		typedef sc::transition<
			events::push_power_button, power_off
		> reactions;
	};
	
	// bit_0 は power_on の内部状態のひとつ。
	struct bit_0 : sc::simple_state< bit_0, power_on >
	{
		typedef sc::transition<
			events::push_flip_button, bit_1
		> reactions;
		
		bit_0() { cout << " bit_0" << endl; }
	};
	
	// bit_1 は power_on の内部状態のひとつ。
	struct bit_1 : sc::simple_state< bit_1, power_on >
	{
		typedef sc::transition<
			events::push_flip_button, bit_0
		> reactions;
		
		bit_1() { cout << " bit_1" << endl; }
	};
	
	// power_off は machine の状態のひとつ。
	struct power_off : sc::simple_state< power_off, machine >
	{
		typedef sc::transition<
			events::push_power_button, power_on
		> reactions;
	};
}

で、これを使ってみます。

int main()
{
	// power_off で初期化。
	machine the_machine;
	
	// flip_button イベントは power_off では無視される。
	the_machine.process_event( events::push_flip_button() );
	
	// power_on :: bit_0 になる。
	the_machine.process_event( events::push_power_button() );
	
	// flip_button イベントを受理。power_on :: bit_1 に遷移。
	the_machine.process_event( events::push_flip_button() );
	
	// power_off になる。
	the_machine.process_event( events::push_power_button() );
	
	// もういちど power_on。
	the_machine.process_event( events::push_power_button() );
}

出力


bit_0
bit_1
bit_0

Boost.statechart では、ある状態 A から状態 B に遷移すると、A のデストラクタ、B のコンストラクタが呼ばれるようになっています。bit_0, bit_1 のコンストラクタが適切な順番で呼ばれているのがわかりますね。

で、このコードのポイントは simple_state<> の型引数です。
先頭は自分自身です。
2 番目のパラメータは "自分が属する親コンテキスト" です。bit_0 の親は power_on 。power_on の親は machine ですね。
3 番目のパラメータは "自分に属する子の初期値" です。power_on に bit_0 の指定があるのがそれです。

 こうして見ると、state_machine は単に "一番上位の親コンテキスト" と見る事が出来ます。

さて、ついでなのでもうひとつ、Boost.statechart の機能を紹介します。
power_off の定義をちょっといじってみます。
状態は、イベントの処理を延期するかどうかを選ぶ事が出来ます。

namespace states
{
	// power_off は machine の状態のひとつ。
	struct power_off : sc::simple_state< power_off, machine >
	{
		typedef boost::mpl::list<
			// push_power_button イベントで power_on になる。
			sc::transition< events::push_power_button, power_on >,
			// push_flip_button イベントは延期させる。
			sc::deferral< events::push_flip_button >
		> reactions;
	};
}

sc::deferral<> が延期の表明です。
ここで指定したイベントは、この状態を脱出した後、次の状態に渡されます。

int main()
{
	// power_off で初期化。
	machine the_machine;
	
	// flip_button イベントは延期され、保持される。
	the_machine.process_event( events::push_flip_button() );

	cout << "deferral" << endl;
	
	// power_on :: bit_0 になる。しかしその直後、
	// 延期された push_flip_button イベントが受理されて bit_1 になる。
	the_machine.process_event( events::push_power_button() );
	
}

出力

deferral
bit_0
bit_1