C++ Boost

Boost Statechart 庫

Tutorial 指南


A Japanese translation of an earlier version of this tutorial can be found at http://prdownloads.sourceforge.jp/jyugem/7127/fsm-tutorial-jp.pdf. Kindly contributed by Mitsuo Fukasawa.

本指南早期版本的日語譯文請見 http://prdownloads.sourceforge.jp/jyugem/7127/fsm-tutorial-jp.pdf. 這是 Mitsuo Fukasawa 的貢獻。

Contents 目錄

Introduction 簡介
How to read this tutorial 如何閱讀本指南
Hello World!
Basic topics: A stop watch 基本議題:秒錶
Defining states and events 定義狀態和事件
Adding reactions 加入反應
State-local storage 狀態局部存儲
Getting state information out of the machine 從狀態機之外獲取狀態信息
Intermediate topics: A digital camera 中級議題:數碼相機
Spreading a state machine over multiple translation units 將一個狀態機分到多個編譯單元
Deferring events 事件延遲
Guards 警戒
In-state reactions 狀態內反應
Transition actions 轉換動作
Advanced topics 高級議題
Specifying multiple reactions for a state 為一個狀態指定多個反應
Posting events 置入事件
History 歷史
Orthogonal states 正交狀態
State queries 狀態查詢
State type information 狀態類型信息
Exception handling 異常處理
Submachines & Parametrized States 子狀態機與參數化狀態
Asynchronous state machines 異步狀態機

Introduction 簡介

The Boost Statechart library is a framework that allows you to quickly transform a UML statechart into executable C++ code, without needing to use a code generator. Thanks to support for almost all UML features the transformation is straight-forward and the resulting C++ code is a nearly redundancy-free textual description of the statechart.
Boost Statechart 庫是一個允許你將一個 UML 狀態圖快速轉換為可執行C++代碼的框架,不需要使用代碼生成器。得益於對幾乎所有 UML 特性的支持,這一轉換工作是很直接了當的,得到的C++代碼也接近於對狀態圖的無冗余文本描述。

How to read this tutorial 如何閱讀本指南

This tutorial was designed to be read linearly. First time users should start reading right at the beginning and stop as soon as they know enough for the task at hand. Specifically:
本指南被設計為按順序來閱讀。用戶第一次閱讀應該從最前面開始,在弄清楚如何解決手上的問題就可以停下來。具體地說:

Hello World!

We will use the simplest possible program to make our first steps. The statechart ...
我們用一個最簡單的程序來開始第一步。狀態圖 ...

HelloWorld

... is implemented with the following code:
... 可以用以下代碼來實現:

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

namespace sc = boost::statechart;

// We are declaring all types as structs only to avoid having to
// type public. If you don't mind doing so, you can just as well
// use class. 我們將所有類型定義為 struct,只是為了避免輸入 public 關鍵字。
// 如果你不介意的話,你也可以使用 class

// We need to forward-declare the initial state because it can
// only be defined at a point where the state machine is
// defined. 我們需要前向聲明初始的狀態,因為它只能在定義了狀態機之後再定義。
struct Greeting;

// Boost.Statechart makes heavy use of the curiously recurring
// template pattern. The deriving class must always be passed as
// the first parameter to all base class templates.
// Boost.Statechart 大量使用了CRTP。派生類必須作為第一個參數傳給所有基類模板。
//
// The state machine must be informed which state it has to
// enter when the machine is initiated. That's why Greeting is
// passed as the second template parameter. 狀態機必須知道在初始化時要
// 進入哪個狀態。所以 Greeting 要作為第二個模板參數傳入。
struct Machine : sc::state_machine< Machine, Greeting > {};

// For each state we need to define which state machine it
// belongs to and where it is located in the statechart. Both is
// specified with Context argument that is passed to
// simple_state<>. For a flat state machine as we have it here,
// the context is always the state machine. Consequently,
// Machine must be passed as the second template parameter to
// Greeting's base (the Context parameter is explained in more
// detail in the next example). 對於每個狀態,我們都需要定義它屬於哪個狀態
// 機,以及它位於狀態圖的哪個位置。這兩者由傳給 simple_state<> 的 Context
// 參數來指定。對於一個像這個例子這樣的平面狀態機,其上下文永遠都是狀態機本身。
// 因此,Machine 必須作為第二個模板參數傳給 Greeting 的基類(在後面的例子中,
// 會更加詳細地解釋 Context 參數)。
struct Greeting : sc::simple_state< Greeting, Machine >
{
// Whenever the state machine enters a state, it creates an
// object of the corresponding state class. The object is then
// kept alive as long as the machine remains in the state.
// Finally, the object is destroyed when the state machine
// exits the state. Therefore, a state entry action can be
// defined by adding a constructor and a state exit action can
// be defined by adding a destructor. 無論何時,當狀態機進入一個狀態時,
// 它會創建相應狀態類的一個對象。這個對象在狀態機處於該狀態的期間保持其生命。最
// 後,當狀態機退出該狀態時,該對像被銷毀。因此,狀態的進入動作可以通過增加一個
// 構造函數來定義,而狀態的退出動作則可以通過增加一個析構函數來定義。
Greeting() { std::cout << "Hello World!\n"; } // entry 進入
~Greeting() { std::cout << "Bye Bye World!\n"; } // exit 退出
};

int main()
{
Machine myMachine;
// The machine is not yet running after construction. We start
// it by calling initiate(). This triggers the construction of
// the initial state Greeting. 狀態機在構造後並不馬上運行。我們要通過
// 調用 initiate() 來讓它開始運行。這時才觸發初始狀態 Greeting 的構造。 
myMachine.initiate();
// When we leave main(), myMachine is destructed what leads to
// the destruction of all currently active states. 當我們離開
// main() 時,myMachine 將被析構並引起所有當前活動的狀態的析構。
return 0;
}

This prints Hello World! and Bye Bye World! before exiting.
這個程序在退出前將打印 Hello World!Bye Bye World! 

Basic topics: A stop watch 基本議題:秒錶

Next we will model a simple mechanical stop watch with a state machine. Such watches typically have two buttons:
接下來我們將用狀態機來模擬一個簡單的秒錶。這種秒錶通常有兩個按鈕:

And two states:
和兩個狀態:

Here is one way to specify this in UML:
以下是以UML方式給出的狀態圖:

StopWatch

Defining states and events 定義狀態和事件

The two buttons are modeled by two events. Moreover, we also define the necessary states and the initial state. The following code is our starting point, subsequent code snippets must be inserted:
兩個按鈕被模擬為兩個事件。此外,我們還將定義所需的狀態和初始狀態。以下代碼只是我們的開始,後面還要插入一些代碼片斷:

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

namespace sc = boost::statechart;

struct EvStartStop : sc::event< EvStartStop > {};
struct EvReset : sc::event< EvReset > {};

struct Active;
struct StopWatch : sc::state_machine< StopWatch, Active > {};

struct Stopped;

// The simple_state class template accepts up to four parameters:
// simple_state 類模板接受四個參數:
// - The third parameter specifies the inner initial state, if
// there is one. Here, only Active has inner states, which is
// why it needs to pass its inner initial state Stopped to its
// base. 第三個參數指定內層的初始狀態,如果有的話。這裡,只有 Active 具有內
// 層狀態,因此要將它的內層初始狀態 Stopped 傳給它的基類。
// - The fourth parameter specifies whether and what kind of
// history is kept. 第四個參數指定是否保持歷史及保持何種歷史

// Active is the outermost state and therefore needs to pass the
// state machine class it belongs to. Active是最外層的狀態,因此要被傳
// 遞給所屬的狀態機。
struct Active : sc::simple_state<
Active, StopWatch, Stopped > {};

// Stopped and Running both specify Active as their Context,
// which makes them nested inside Active. Stopped和Running都將Active
// 指定為它們的Context,這樣就將它們嵌套在Active的裡面了。
struct Running : sc::simple_state< Running, Active > {};
struct Stopped : sc::simple_state< Stopped, Active > {};

// Because the context of a state must be a complete type (i.e.
// not forward declared), a machine must be defined from
// "outside to inside". That is, we always start with the state
// machine, followed by outermost states, followed by the direct
// inner states of outermost states and so on. We can do so in a
// breadth-first or depth-first way or employ a mixture of the
// two. 因為一個狀態的context必須是一個完整的類型(即不可以是前向聲明的),所以
// 狀態機必須"由外而內"地進行定義。即,我們總是從狀態機開始,接下來是最外層的狀態,
// 然後是最外層狀態的直接內層狀態,等等。我們可以以寬度優先、深度優先或兩者混合的
// 方式來進行。

int main()
{
StopWatch myWatch;
myWatch.initiate();
return 0;
}

This compiles but doesn't do anything observable yet.
這段代碼可以編譯,但還沒有任何看得見的動作。

Adding reactions 加入反應

For the moment we will use only one type of reaction: transitions. We insert the bold parts of the following code:
在這一刻,我們只使用一種反應類型:轉換。我們插入以 下代碼的粗體部分:

#include <boost/statechart/transition.hpp>

// ...
struct Stopped;
struct Active : sc::simple_state< Active, StopWatch, Stopped >
{
typedef sc::transition< EvReset, Active > reactions;
};
struct Running : sc::simple_state< Running, Active >
{
typedef sc::transition< EvStartStop, Stopped > reactions;
};
struct Stopped : sc::simple_state< Stopped, Active >
{
typedef sc::transition< EvStartStop, Running > reactions;
};

// A state can define an arbitrary number of reactions. That's
// why we have to put them into an mpl::list<> as soon as there
// is more than one of them (see Specifying multiple reactions
// for a state). 一個狀態可以定義任意數量的反應。因此當反應多於一個時,我們
// 要將它們放入一個 mpl::list<> 中 (請見 對一個狀態指定多個反應)。

int main()
{
StopWatch myWatch;
myWatch.initiate();
myWatch.process_event( EvStartStop() );
myWatch.process_event( EvStartStop() );
myWatch.process_event( EvStartStop() );
myWatch.process_event( EvReset() );
return 0;
}

Now we have all the states and all the transitions in place and a number of events are also sent to the stop watch. The machine dutifully makes the transitions we would expect, but no actions are executed yet.
現我們有了所有的狀態和所有的轉換,而且還有一系列事件被發送給了這個秒錶。這個狀態機將忠實地按我們的期望進行狀態的轉換,不過仍然沒有動作被執行。

State-local storage 狀態局部存儲

Next we'll make the stop watch actually measure time. Depending on the state the stop watch is in, we need different variables:
接下來我們將讓這個秒錶真正進行時間的測量。取決於秒錶所處的狀態,我們需要一些不同的變量:

We observe that the elapsed time variable is needed no matter what state the machine is in. Moreover, this variable should be reset to 0 when we send an EvReset event to the machine. The other variable is only needed while the machine is in the Running state. It should be set to the current time of the system clock whenever we enter the Running state. Upon exit we simply subtract the start time from the current system clock time and add the result to the elapsed time.
我們看到無論狀態機處於哪個狀態,保存已測量時間的變量都是需要的。此外,這個變量會在我們向狀態機發出一個 EvReset 事件時被重置為0。另一個變量則只在狀態機處於 Running 狀態時才需要。它應在我們進入 Running 狀態時被設置為系統時鐘的當前時間。到退出時,我們只要從系統時鐘的當前時間中減去這個開始時間,再將結果加到已測量時間上就可以了。

#include <ctime>

// ...
struct Stopped;
struct Active : sc::simple_state< Active, StopWatch, Stopped >
{
public:
typedef sc::transition< EvReset, Active > reactions;
Active() : elapsedTime_( 0.0 ) {}
double ElapsedTime() const { return elapsedTime_; }
double & ElapsedTime() { return elapsedTime_; }
private:
double elapsedTime_;
};

struct Running : sc::simple_state< Running, Active >
{
public:
typedef sc::transition< EvStartStop, Stopped > reactions;
Running() : startTime_( std::time( 0 ) ) {}
~Running()
{
// Similar to when a derived class object accesses its
// base class portion, context<>() is used to gain
// access to the direct or indirect context of a state.
// This can either be a direct or indirect outer state
// or the state machine itself (e.g. here: context< StopWatch >()).
// 與一個派生類對像訪問它的基類部分相類似,context<>() 用於訪問一個
// 狀態的直接或間接上下文。這可以是直接或間接外層狀態,或者是狀態機本身
// (如:context< StopWatch >())。
context< Active >().ElapsedTime() +=
std::difftime( std::time( 0 ), startTime_ );
}
private:
std::time_t startTime_;
};

// ...

The machine now measures the time, but we cannot yet retrieve it from the main program.
現在這個狀態機可以測量時間了,但是我們還不能在主程序中取出這個測量的結果。

At this point, the advantages of state-local storage (which is still a relatively little-known feature) may not yet have become apparent. The FAQ item "What's so cool about state-local storage?" tries to explain them in more detail by comparing this StopWatch with one that does not make use of state-local storage.
到目前為止,狀態局部存儲(還只是一個相對不太知名的特性)的好處還不是很明顯。FAQ條目"狀態局部存儲有什麼好處?"試圖給出 更加詳細的解釋,那裡會將這個秒錶實現與另一個不使用狀態局存儲的實現進行比較。

Getting state information out of the machine 從狀態機之外獲取狀態信息

To retrieve the measured time, we need a mechanism to get state information out of the machine. With our current machine design there are two ways to do that. For the sake of simplicity we use the less efficient one: state_cast<>() (StopWatch2.cpp shows the slightly more complex alternative). As the name suggests, the semantics are very similar to the ones of dynamic_cast. For example, when we call myWatch.state_cast< const Stopped & >() and the machine is currently in the Stopped state, we get a reference to the Stopped state. Otherwise std::bad_cast is thrown. We can use this functionality to implement a StopWatch member function that returns the elapsed time. However, rather than ask the machine in which state it is and then switch to different calculations for the elapsed time, we put the calculation into the Stopped and Running states and use an interface to retrieve the elapsed time:
要獲得被測量的時間量,我們需要一個機制從狀態機之外獲取狀態的信息。對於我們現在的狀態機設計,有兩種方法。為簡單起見,我們使用效率較差的一種:state_cast<>() (StopWatch2.cpp 示範了稍微複雜一點的另一種方法)。顧名思義,它的語義非常類似於 dynamic_cast. 例如,當我們調用 myWatch.state_cast< const Stopped & >() 狀態機正處於 Stopped 狀態時,我們將得到一個 Stopped 狀態的引用。否則拋出 std::bad_cast 異常。我們可以利用這個功能來實現一個 StopWatch 成員函數,返回被測量的時間。不過,與其詢問狀態機處於何種狀態然後轉換到不同的運算以獲得被測量時間,我們不如將這個運算置入 Stopped 和 Running 狀態,然後用一個接口來取出被測量時間:

#include <iostream>

// ...

struct IElapsedTime
{
virtual double ElapsedTime() const = 0;
};

struct Active;
struct StopWatch : sc::state_machine< StopWatch, Active >
{
double ElapsedTime() const
{
return state_cast< const IElapsedTime & >().ElapsedTime();
}
};

// ...

struct Running : IElapsedTime,
sc::simple_state< Running, Active >
{
public:
typedef sc::transition< EvStartStop, Stopped > reactions;
Running() : startTime_( std::time( 0 ) ) {}
~Running()
{
context< Active >().ElapsedTime() = ElapsedTime();
}

virtual double ElapsedTime() const
{
return context< Active >().ElapsedTime() +
std::difftime( std::time( 0 ), startTime_ );
}
private:
std::time_t startTime_;
};

struct Stopped : IElapsedTime,
sc::simple_state< Stopped, Active >
{
typedef sc::transition< EvStartStop, Running > reactions;
virtual double ElapsedTime() const
{
return context< Active >().ElapsedTime();
}
};

int main()
{
StopWatch myWatch;
myWatch.initiate();
std::cout << myWatch.ElapsedTime() << "\n";
myWatch.process_event( EvStartStop() );
std::cout << myWatch.ElapsedTime() << "\n";
myWatch.process_event( EvStartStop() );
std::cout << myWatch.ElapsedTime() << "\n";
myWatch.process_event( EvStartStop() );
std::cout << myWatch.ElapsedTime() << "\n";
myWatch.process_event( EvReset() );
std::cout << myWatch.ElapsedTime() << "\n";
return 0;
}

To actually see time being measured, you might want to single-step through the statements in main(). The StopWatch example extends this program to an interactive console application.
要真正看到被測量的時間,你可能需要單步執行 main() 中的語句。例子 StopWatch 將以上程序擴展為交互式的控制台應用。

Intermediate topics: A digital camera 中級議題:數碼相機

So far so good. However, the approach presented above has a few limitations:
到目前為止還都不錯。不過,前面介紹的方法具有一些局限性:

All these limitations can be overcome with custom reactions. Warning: It is easy to abuse custom reactions up to the point of invoking undefined behavior. Please study the documentation before employing them!
所有這些局限可以通過定制化反應來克服。警告:濫用定制化反應很容易導致未定義行為。在使用它們之前請學習本文檔!

Spreading a state machine over multiple translation units 將一個狀態機分到多個編譯單元

Let's say your company would like to develop a digital camera. The camera has the following controls:
下面我們假設你的公司要開發一個數碼相機。該相機有以下控制鍵:

One use case for the camera says that the photographer can half-press the shutter anywhere in the configuration mode and the camera will immediately go into shooting mode. The following statechart is one way to achieve this behavior:
這個相機的一個用例是,攝像者可以在配置模式的任何地方半 按快門,相機會立即進入照相模式。以下狀態圖是完成這一行為的一個方法:

Camera

The Configuring and Shooting states will contain numerous nested states while the Idle state is relatively simple. It was therefore decided to build two teams. One will implement the shooting mode while the other will implement the configuration mode. The two teams have already agreed on the interface that the shooting team will use to retrieve the configuration settings. We would like to ensure that the two teams can work with the least possible interference. So, we put the two states in their own translation units so that machine layout changes within the Configuring state will never lead to a recompilation of the inner workings of the Shooting state and vice versa.
Configuring 和 Shooting 狀態中包含有多個嵌套狀態,而 Idle 狀態則相對簡單。因此我們決定建立兩個團隊。一個負責實現照相模式而另一個負責實現配置模式。這兩個團隊已經對接口達到一致,shooting 團隊使用這一接口獲取配置設置。我們應該確認這兩個團隊可以以最小的衝突一起工作。因此,我們將這兩個狀態放入各自的編譯單元,以便在 Configuring 狀態內的狀態機佈局的變化不會引起 Shooting 狀態內部的重編譯,反之亦然。

Unlike in the previous example, the excerpts presented here often outline different options to achieve the same effect. That's why the code is often not equal to the Camera example code. Comments mark the parts where this is the case.
與前一個例子不同,這裡給出的摘錄描述了達到同一效果的不同選項。所以這些代碼與 Camera 例子的代碼是不同的。我 們用註釋標出有這種情況的地方。

Camera.hpp:

#ifndef CAMERA_HPP_INCLUDED
#define CAMERA_HPP_INCLUDED

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

namespace sc = boost::statechart;

struct EvShutterHalf : sc::event< EvShutterHalf > {};
struct EvShutterFull : sc::event< EvShutterFull > {};
struct EvShutterRelease : sc::event< EvShutterRelease > {};
struct EvConfig : sc::event< EvConfig > {};

struct NotShooting;
struct Camera : sc::state_machine< Camera, NotShooting >
{
bool IsMemoryAvailable() const { return true; }
bool IsBatteryLow() const { return false; }
};

struct Idle;
struct NotShooting : sc::simple_state<
NotShooting, Camera, Idle >
{
// With a custom reaction we only specify that we might do
// something with a particular event, but the actual reaction
// is defined in the react member function, which can be
// implemented in the .cpp file. 對於定制化反應,我們只需指定對於特定
// 事件我們可能做些什麼,而實際的反應是定義在 react 成員函數中的,它可以在
// .cpp 文件中實現。
typedef sc::custom_reaction< EvShutterHalf > reactions;
// ...
sc::result react( const EvShutterHalf & );
};
struct Idle : sc::simple_state< Idle, NotShooting >
{
typedef sc::custom_reaction< EvConfig > reactions;
// ...
sc::result react( const EvConfig & );
};
#endif

Camera.cpp:

#include "Camera.hpp"

// The following includes are only made here but not in
// Camera.hpp
// The Shooting and Configuring states can themselves apply the
// same pattern to hide their inner implementation, which
// ensures that the two teams working on the Camera state
// machine will never need to disturb each other.
// 以下頭文件是只用於本文件而不用於 Camera.hpp 的
// Shooting 和 Configuring 狀態可以使用相同的模式來隱藏它們內部的實現,
// 以確保實現 Camera 狀態機的這兩個團隊不會相互干擾。
#include "Configuring.hpp"
#include "Shooting.hpp"

// ...

// not part of the Camera example 這不是 Camera 例子的一部分
sc::result NotShooting::react( const EvShutterHalf & )
{
return transit< Shooting >();
}

sc::result Idle::react( const EvConfig & )
{
return transit< Configuring >();
}

Caution: Any call to simple_state<>::transit<>() or simple_state<>::terminate() (see reference) will inevitably destruct the state object (similar to delete this;)! That is, code executed after any of these calls may invoke undefined behavior! That's why these functions should only be called as part of a return statement.
警告:任何對 simple_state<>::transit<>()simple_state<>::terminate() (請見參考)的調用都不可避免地 會析構這個狀態對像(類似於 delete this;)! 即,任何在這些調用之後的代碼執行都會引起未定義行為!
所以這些函數只能作為 return 語句的一部分來調用。

Deferring events 事件延遲

The inner workings of the Shooting state could look as follows:
Shooting 狀態的內部工作可能會如下圖:

Camera2

When the user half-presses the shutter, Shooting and its inner initial state Focusing are entered. In the Focusing entry action the camera instructs the focusing circuit to bring the subject into focus. The focusing circuit then moves the lenses accordingly and sends the EvInFocus event as soon as it is done. Of course, the user can fully-press the shutter while the lenses are still in motion. Without any precautions, the resulting EvShutterFull event would simply be lost because the Focusing state does not define a reaction for this event. As a result, the user would have to fully-press the shutter again after the camera has finished focusing. To prevent this, the EvShutterFull event is deferred inside the Focusing state. This means that all events of this type are stored in a separate queue, which is emptied into the main queue when the Focusing state is exited.
當用戶半按快門時,將進入 Shooting 及其內部初始狀態 Focusing. 在進入 Focusing 的動作中,相機將指示聚焦電路將被照物聚焦。聚焦電路據此移動鏡頭,並在完成聚焦後立即發送 EvInFocus 事件。當然,用戶也可以在鏡頭仍在移動時就完全按下快門。由此產生的 EvShutterFull 事件會在沒有任何警告下被拋棄,因為 Focusing 狀態沒有為該事件定義反應。因此,必須在相機聚焦完成後再全按快門。為防止這種情況,EvShutterFull 事件將在 Focusing 狀態內被延遲。這意味著所有這類事件將被保存在一個獨立的序列中,在退出 Focusing 狀態時,該隊列將被全部轉移到主隊列中。

struct Focusing : sc::state< Focusing, Shooting >
{
typedef mpl::list<
sc::custom_reaction< EvInFocus >,
sc::deferral< EvShutterFull >
> reactions;
Focusing( my_context ctx );
sc::result react( const EvInFocus & );
};

Guards 警戒

Both transitions originating at the Focused state are triggered by the same event but they have mutually exclusive guards. Here is an appropriate custom reaction:
在 Focused 狀態發生的兩個轉換是由同一個事件觸發的,但是它們具有互斥的警戒。以下是一個恰當的定制化反應:

// not part of the Camera example 不是 Camera 例子中的一部分
sc::result Focused::react( const EvShutterFull & )
{
if ( context< Camera >().IsMemoryAvailable() )
{
return transit< Storing >();
}
else
{
// The following is actually a mixture between an in-state
// reaction and a transition. See later on how to implement
// proper transition actions. 以下實際上是狀態內反應和狀態轉換的一
// 個混合物。如何實現正確的轉換動作請見下文。
std::cout << "Cache memory full. Please wait...\n";
return transit< Focused >();
}
}

Custom reactions can of course also be implemented directly in the state declaration, which is often preferable for easier browsing.
當然,定制化反應也可以直接在狀態聲明中實現,對於易讀性來說這樣做更可取。

Next we will use a guard to prevent a transition and let outer states react to the event if the battery is low:
下面我們將使用一個警戒來在電池不足時阻止狀態轉換,並讓外層的狀態對此事件作出反應:

Camera.cpp:

// ...
sc::result NotShooting::react( const EvShutterHalf & )
{
if ( context< Camera >().IsBatteryLow() )
{
// We cannot react to the event ourselves, so we forward it
// to our outer state (this is also the default if a state
// defines no reaction for a given event). 我們不能自己對此事件
// 作出反應,因此我們將它前轉到外層的狀態(這也是一個狀態沒有對給定的事件
// 定義反應時的缺省行為)。
return forward_event();
}
else
{
return transit< Shooting >();
}
}
// ...

In-state reactions 狀態內反應

The self-transition of the Focused state could also be implemented as an in-state reaction, which has the same effect as long as Focused does not have any entry or exit actions:
Focused 狀態的自轉換也可以實現為一個 狀 態內反應,只要 Focused 沒有任何進入或退出動作,其效果就是一樣的:

Shooting.cpp:

// ...
sc::result Focused::react( const EvShutterFull & )
{
if ( context< Camera >().IsMemoryAvailable() )
{
return transit< Storing >();
}
else
{
std::cout << "Cache memory full. Please wait...\n";
// Indicate that the event can be discarded. So, the
// dispatch algorithm will stop looking for a reaction
// and the machine remains in the Focused state. 表示該
// 事件可以被拋棄。因此,事件分派算法將停止查找反應,且狀態機保持為
// Focused 狀態。
return discard_event();
}
}
// ...

Because the in-state reaction is guarded, we need to employ a custom_reaction<> here. For unguarded in-state reactions in_state_reaction<> should be used for better code-readability.
因為狀態內反應是被警戒的,我們需要在這裡使用一個 custom_reaction<> . 對於無警戒的狀態內反應,可以使用 in_state_reaction<> 來增加代碼可讀性。

Transition actions 轉換動作

As an effect of every transition, actions are executed in the following order:
作為每次轉換的結果,動作將按以下順序被執行:

  1. Starting from the innermost active state, all exit actions up to but excluding the innermost common context
  2. The transition action (if present)
  3. Starting from the innermost common context, all entry actions down to the target state followed by the entry actions of the initial states
  1. 從最內層的活動狀態開始,向上逐層執行所有退出動作,直到但不包括 最內層的公共上下文
  2. 執行轉換動作(如果有)
  3. 從最內層的公共上下文開始,向下逐層執行所有進入動作,直到目標狀態,然後執行其初始狀態的進入動作

Example:
例子:

LCA

Here the order is as follows: ~D(), ~C(), ~B(), ~A(), t(), X(), Y(), Z(). The transition action t() is therefore executed in the context of the InnermostCommonOuter state because the source state has already been left (destructed) and the target state has not yet been entered (constructed).
這裡的執行順序如下:~D(), ~C(), ~B(), ~A(), t(), X(), Y(), Z(). 因此轉換動作 t() 是在 InnermostCommonOuter 狀態的上下文中執行的,因為原來的狀態已經離開(析構)而目標狀態尚未進入(構造)。

With Boost.Statechart, a transition action can be a member of any common outer context. That is, the transition between Focusing and Focused could be implemented as follows:
對於 Boost.Statechart, 轉換動作可以是任 何公共外層上下文的成員。即,Focusing 和 Focused 間的轉換應該實現如下:

Shooting.hpp:

// ...
struct Focusing;
struct Shooting : sc::simple_state< Shooting, Camera, Focusing >
{
typedef sc::transition<
EvShutterRelease, NotShooting > reactions;

// ...
void DisplayFocused( const EvInFocus & );
};
// ...
// not part of the Camera example 不是 Camera 例子的一部分
struct Focusing : sc::simple_state< Focusing, Shooting >
{
typedef sc::transition< EvInFocus, Focused,
Shooting, &Shooting::DisplayFocused > reactions;
};

Or, the following is also possible (here the state machine itself serves as the outermost context):
或者,
下面這樣也可以(這時,狀態機自己被當作最外層的上下文):

// not part of the Camera example 不是 Camera 例子的一部分 
struct Camera : sc::state_machine< Camera, NotShooting >
{
void DisplayFocused( const EvInFocus & );
};
// not part of the Camera example 不是 Camera 例子的一部分
struct Focusing : sc::simple_state< Focusing, Shooting >
{
typedef sc::transition< EvInFocus, Focused,
Camera, &Camera::DisplayFocused > reactions;
};

Naturally, transition actions can also be invoked from custom reactions:
自然,轉換動作也可以從定制化反應中進行調用:

Shooting.cpp:

// ...
sc::result Focusing::react( const EvInFocus & evt )
{
// We have to manually forward evt 我們必須手工前轉 evt
return transit< Focused >( &Shooting::DisplayFocused, evt );
}

Advanced topics 高級議題

Specifying multiple reactions for a state 為一個狀態指定多個反應

Often a state must define reactions for more than one event. In this case, an mpl::list<> must be used as outlined below:
通常,一個狀態必須為多個事件定義反應。在這種情況下,必須如下所示使用 mpl::list<>:

// ...

#include <boost/mpl/list.hpp>

namespace mpl = boost::mpl;

// ...
struct Playing : sc::simple_state< Playing, Mp3Player >
{
typdef mpl::list<
sc::custom_reaction< EvFastForward >,
sc::transition< EvStop, Stopped > > reactions;

/* ... */
};

Posting events 置入事件

Non-trivial state machines often need to post internal events. Here's an example of how to do this:
非平凡的狀態機通常都需要置入內部事件。以下是關於如何做這件事的一個例子:

Pumping::~Pumping()
{
post_event( EvPumpingFinished() );
}

The event is pushed into the main queue. The events in the queue are processed as soon as the current reaction is completed. Events can be posted from inside react functions, entry-, exit- and transition actions. However, posting from inside entry actions is a bit more complicated (see e.g. Focusing::Focusing() in Shooting.cpp in the Camera example):
該事件被放入主隊列。一旦當前反應完成後,就會立即處理隊列中的事件。事件可以由內部的 react 函數、或進入、退出和轉換動作來置入。但是,由內部進入動作來置入事件有一點複雜(請見 Camera 例子的 Shooting.cpp 中的 Focusing::Focusing()):

struct Pumping : sc::state< Pumping, Purifier >
{
Pumping( my_context ctx ) : my_base( ctx )
{
post_event( EvPumpingStarted() );
}
// ...
};

As soon as an entry action of a state needs to contact the "outside world" (here: the event queue in the state machine), the state must derive from state<> rather than from simple_state<> and must implement a forwarding constructor as outlined above (apart from the constructor, state<> offers the same interface as simple_state<>). Hence, this must be done whenever an entry action makes one or more calls to the following functions:
一旦狀態的進入動作要聯繫"外部世界"(在此即為:狀態機的事件隊列),那麼該狀態必須要派生自 state<> 而不是 simple_state<> 且必須實現一個前轉構造函數,如上所示(除了構造函數以外,state<> 提供了與 simple_state<> 相同的接口)。因此,無論何時一個進入動作要調用以下一個或多個函數,都必須這樣做:

In my experience, these functions are needed only rarely in entry actions so this workaround should not uglify user code too much.
以我的經驗,在進入動作中很少需要這些函數,所以這一方法不會使得用戶代碼太過難看。

History 歷史

Photographers testing beta versions of our digital camera said that they really liked that half-pressing the shutter anytime (even while the camera is being configured) immediately readies the camera for picture-taking. However, most of them found it unintuitive that the camera always goes into the idle mode after releasing the shutter. They would rather see the camera go back into the state it had before half-pressing the shutter. This way they can easily test the influence of a configuration setting by modifying it, half- and then fully-pressing the shutter to take a picture. Finally, releasing the shutter will bring them back to the screen where they have modified the setting. To implement this behavior we'd change the state chart as follows:
對我們的數 碼相機進 行測試的攝像者說,他們希望無論何時半按快門(即使相機正在配置中)都可以讓相機立即準備好照相。不過,多數人覺得在釋放快門後相機總是進入空閒模式,這 一點不太直觀。他們希望看到相機可以回到半按快門之前的狀態。這樣他們可以很容易地通過修改配置項的設置、半按再全按快門照相,來測試配置項的影響。最 後,釋放快門後會將他們帶回到修改設置的屏幕。要實現這一行為,我們要將狀態圖修改如下:

CameraWithHistory1

As mentioned earlier, the Configuring state contains a fairly complex and deeply nested inner machine. Naturally, we'd like to restore the previous state down to the innermost state(s) in Configuring, that's why we use a deep history pseudo state. The associated code looks as follows:
前面提到過,Configuring 狀態包含了相當複雜且具有一定深度的內層嵌套狀態機。自然,我們要恢復 Configuring 中直至最內層狀態的舊狀態,所以 我們要使用深歷史偽狀態。有關代碼如下:

// not part of the Camera example 不是 Camera 例子的一部分
struct NotShooting : sc::simple_state<
NotShooting, Camera, Idle, sc::has_deep_history >
{
// ...
};

// ...

struct Shooting : sc::simple_state< Shooting, Camera, Focusing >
{
typedef sc::transition<
EvShutterRelease, sc::deep_history< Idle > > reactions;

// ...
};

History has two phases: Firstly, when the state containing the history pseudo state is exited, information about the previously active inner state hierarchy must be saved. Secondly, when a transition to the history pseudo state is made later, the saved state hierarchy information must be retrieved and the appropriate states entered. The former is expressed by passing either has_shallow_history, has_deep_history or has_full_history (which combines shallow and deep history) as the last parameter to the simple_state and state class templates. The latter is expressed by specifying either shallow_history<> or deep_history<> as a transition destination or, as we'll see in an instant, as an inner initial state. Because it is possible that a state containing a history pseudo state has never been entered before a transition to history is made, both class templates demand a parameter specifying the default state to enter in such situations.
歷史具有兩個階段:首先,當包含歷史偽狀態的狀態被退出時,有關舊的活動內層狀態層次的信息必須被保存。其次,當稍後要轉換到該歷史偽狀態時,被保存的層 次信息必須被取回並進入到相應的狀態。前者通過將 has_shallow_history, has_deep_historyhas_full_history (其結合了淺歷史和深歷史) 作為 simple_statestate 類模板的最後一個參數傳入來表示。後者則通過將 shallow_history<>deep_history<> 指定為狀態轉換目標,或者像我們在這個例子中所做的那樣,指定為一個內層初始狀態。因為一個包含了歷史偽狀態的狀態有可能在需要轉換到它的歷史之前從未被 進入過,所以這兩個類模板需要一個參數來指定在這種情況下要進入的缺省狀態。

The redundancy necessary for using history is checked for consistency at compile time. That is, the state machine wouldn't have compiled had we forgotten to pass has_deep_history to the base of NotShooting.
為了使用歷史,需要在編譯期對一致性進行檢查。即,如果我們忘記將 has_deep_history 傳入到 NotShooting 的基類,這個狀態機將無法編譯。

Another change request filed by a few beta testers says that they would like to see the camera go back into the state it had before turning it off when they turn it back on. Here's the implementation:
少數測試者提出了另一個修改要求,他們希望看到在相機重新開機時可以回到關機前的狀態。以下是相應的實現:

CameraWithHistory2

// ...

// not part of the Camera example 不是 Camera 例子的一部分
struct NotShooting : sc::simple_state< NotShooting, Camera,
mpl::list< sc::deep_history< Idle > >,
sc::has_deep_history >
{
// ...
};

// ...

Unfortunately, there is a small inconvenience due to some template-related implementation details. When the inner initial state is a class template instantiation we always have to put it into an mpl::list<>, although there is only one inner initial state. Moreover, the current deep history implementation has some limitations.
不幸的是,這裡有一個小麻煩,是由一些與模板相關的實現細節所引起的。當內層的初始狀態是一個類模板的實例時,我們就必須將它放入一個 mpl::list<> 中,即使只有一個內層初始狀態。此外,當前的深歷史實現還有一些局 限性

Orthogonal states 正交狀態

OrthogonalStates

To implement this statechart you simply specify more than one inner initial state (see the Keyboard example):
要實現這個狀態圖,你只需指定一個以上的內層初始狀態就可以了(請見 Keyboard 例子):

struct Active;
struct Keyboard : sc::state_machine< Keyboard, Active > {};

struct NumLockOff;
struct CapsLockOff;
struct ScrollLockOff;
struct Active: sc::simple_state< Active, Keyboard,
mpl::list< NumLockOff, CapsLockOff, ScrollLockOff > > {};

Active's inner states must declare which orthogonal region they belong to:
Active 的內層狀態必須聲明它們所屬的正交區域:

struct EvNumLockPressed : sc::event< EvNumLockPressed > {};
struct EvCapsLockPressed : sc::event< EvCapsLockPressed > {};
struct EvScrollLockPressed :
sc::event< EvScrollLockPressed > {};

struct NumLockOn : sc::simple_state<
NumLockOn, Active::orthogonal< 0 > >
{
typedef sc::transition<
EvNumLockPressed, NumLockOff > reactions;
};

struct NumLockOff : sc::simple_state<
NumLockOff, Active::orthogonal< 0 > >
{
typedef sc::transition<
EvNumLockPressed, NumLockOn > reactions;
};

struct CapsLockOn : sc::simple_state<
CapsLockOn, Active::orthogonal< 1 > >
{
typedef sc::transition<
EvCapsLockPressed, CapsLockOff > reactions;
};

struct CapsLockOff : sc::simple_state<
CapsLockOff, Active::orthogonal< 1 > >
{
typedef sc::transition<
EvCapsLockPressed, CapsLockOn > reactions;
};

struct ScrollLockOn : sc::simple_state<
ScrollLockOn, Active::orthogonal< 2 > >
{
typedef sc::transition<
EvScrollLockPressed, ScrollLockOff > reactions;
};

struct ScrollLockOff : sc::simple_state<
ScrollLockOff, Active::orthogonal< 2 > >
{
typedef sc::transition<
EvScrollLockPressed, ScrollLockOn > reactions;
};

orthogonal< 0 > is the default, so NumLockOn and NumLockOff could just as well pass Active instead of Active::orthogonal< 0 > to specify their context. The numbers passed to the orthogonal member template must correspond to the list position in the outer state. Moreover, the orthogonal position of the source state of a transition must correspond to the orthogonal position of the target state. Any violations of these rules lead to compile time errors. Examples:
orthogonal< 0 > 是缺省的,所以 NumLockOnNumLockOff 可以用 Active 來代替 Active::orthogonal< 0 >  指定其上下文。傳遞給 orthogonal 成員模板的數字必須與在外層狀態的 list 中位置相對應。此外,一個轉換的源狀態的正交位置必須與目標狀態的正交位置相對應。對這些規則的任何違反都會導致編譯期錯誤。例如:

// Example 1: does not compile because Active specifies
// only 3 orthogonal regions
// 例1:因為 Active 只指定了3個正交區域,所以不能編譯
struct WhateverLockOn: sc::simple_state<
WhateverLockOn, Active::orthogonal< 3 > > {};

// Example 2: does not compile because Active specifies
// that NumLockOff is part of the "0th" orthogonal region
// 例2:因為 Active 指定了 NumLockOff 屬於第0個正交區域,所以不能編譯
struct NumLockOff : sc::simple_state<
NumLockOff, Active::orthogonal< 1 > > {};

// Example 3: does not compile because a transition between
// different orthogonal regions is not permitted
// 例3:因為不允許不同正交區域間的轉換,所以不能編譯
struct CapsLockOn : sc::simple_state<
CapsLockOn, Active::orthogonal< 1 > >
{
typedef sc::transition<
EvCapsLockPressed, CapsLockOff > reactions;
};

struct CapsLockOff : sc::simple_state<
CapsLockOff, Active::orthogonal< 2 > >
{
typedef sc::transition<
EvCapsLockPressed, CapsLockOn > reactions;
};

State queries 狀態查詢

Often reactions in a state machine depend on the active state in one or more orthogonal regions. This is because orthogonal regions are not completely orthogonal or a certain reaction in an outer state can only take place if the inner orthogonal regions are in particular states. For this purpose, the state_cast<> function introduced under Getting state information out of the machine is also available within states.
通常,一個狀態機中的反應是依賴於一個或多個正交區域的活動狀態的。這是因為正交區域並非完全正交的,或者因為外層狀態的某個反應僅當內層正交區域處於某 個特定狀態時才能發生。為此,在 從 狀態機之外獲取狀態信息 中介紹的 state_cast<> 函數可以在狀態內部使用。

As a somewhat far-fetched example, let's assume that our keyboard also accepts EvRequestShutdown events, the reception of which makes the keyboard terminate only if all lock keys are in the off state. We would then modify the Keyboard state machine as follows:
作為一個有點牽強的例子,我們假定我們的 鍵 盤 還接受 EvRequestShutdown 事件,該事件僅在所有鎖定鍵處於關閉狀態時終止鍵盤。我們需要修改 Keyboard 狀態機如下:

struct EvRequestShutdown : sc::event< EvRequestShutdown > {};

struct NumLockOff;
struct CapsLockOff;
struct ScrollLockOff;
struct Active: sc::simple_state< Active, Keyboard,
mpl::list< NumLockOff, CapsLockOff, ScrollLockOff > >
{
typedef sc::custom_reaction< EvRequestShutdown > reactions;

sc::result react( const EvRequestShutdown & )
{
if ( ( state_downcast< const NumLockOff * >() != 0 ) &&
( state_downcast< const CapsLockOff * >() != 0 ) &&
( state_downcast< const ScrollLockOff * >() != 0 ) )
{
return terminate();
}
else
{
return discard_event();
}
}
};

Passing a pointer type instead of reference type results in 0 pointers being returned instead of std::bad_cast being thrown when the cast fails. Note also the use of state_downcast<>() instead of state_cast<>(). Similar to the differences between boost::polymorphic_downcast<>() and dynamic_cast, state_downcast<>() is a much faster variant of state_cast<>() and can only be used when the passed type is a most-derived type. state_cast<>() should only be used if you want to query an additional base.
通過傳入一個指針類型來替代引用類型,可以在轉型失敗時返回一個0指針而不是拋出 std::bad_cast。 還需要留意使用的是 state_downcast<>() 而不是 state_cast<>(). 它們之間的區別類似於 boost::polymorphic_downcast<>()dynamic_cast 間的區別,state_downcast<>() 是比 state_cast<>() 更快且只能用於傳入的類型是派生層次最底層的類型的情況下。如果你想查詢其它基類,則只能使用 state_cast<>().

Custom state queries 定制化狀態查詢

It is often desirable to find out exactly which state(s) a machine currently resides in. To some extent this is already possible with state_cast<>() and state_downcast<>() but their utility is rather limited because both only return a yes/no answer to the question "Are you in state X?". It is possible to ask more sophisticated questions when you pass an additional base class rather than a state class to state_cast<>() but this involves more work (all states need to derive from and implement the additional base), is slow (under the hood state_cast<>() uses dynamic_cast), forces projects to compile with C++ RTTI turned on and has a negative impact on state entry/exit speed.
通常我們想精確地找出狀態機正處於哪個(些)狀態。state_cast<>()state_downcast<>() 在一定程度上已經可以達到此目的,但是它們的使用還是有局限,因為它們都只是對"你是否處於狀態X?"這樣的問題給出是/否的回答。你可以將其它基類而不 是狀態類傳給 state_cast<>() 來詢問更為複雜的問題,但是這需要更多的工作(所有狀態必須派生自且實現該基類),也更慢(由於 state_cast<>() 使用了 dynamic_cast),並且強制要求工程編譯時要打開 C++ RTTI,這對於狀態的進入/退出速度帶來了負面的影響。

Especially for debugging it would be so much more useful being able to ask "In which state(s) are you?". For this purpose it is possible to iterate over all active innermost states with state_machine<>::state_begin() and state_machine<>::state_end(). Dereferencing the returned iterator returns a reference to const state_machine<>::state_base_type, the common base of all states. We can thus print the currently active state configuration as follows (see the Keyboard example for the complete code):
特別是在調試時,如果可以詢問 "你正處於哪個(些)狀態?",是非常有用的。為此,可能需要對所有最內層的狀態進行迭代,可以使用 state_machine<>::state_begin()state_machine<>::state_end(). 對返回的迭代器進行提領操作,將返回一個對 const state_machine<>::state_base_type 的引用,該類型是所有狀態的公共基類。所以我們可以像下面這樣打印出當前活動的狀態配置(完整的代碼請見 Keyboard 例子):

void DisplayStateConfiguration( const Keyboard & kbd )
{
char region = 'a';

for (
Keyboard::state_iterator pLeafState = kbd.state_begin();
pLeafState != kbd.state_end(); ++pLeafState )
{
std::cout << "Orthogonal region " << region << ": ";
// The following use of typeid assumes that
// BOOST_STATECHART_USE_NATIVE_RTTI is defined
// 以下對 typeid 的使用假設 BOOST_STATECHART_USE_NATIVE_RTTI 已定義
std::cout << typeid( *pLeafState ).name() << "\n";
++region;
}
}

If necessary, the outer states can be accessed with state_machine<>::state_base_type::outer_state_ptr(), which returns a pointer to const state_machine<>::state_base_type. When called on an outermost state this function simply returns 0.
如果有必要,可以通過 state_machine<>::state_base_type::outer_state_ptr() 訪問外層的狀態,它返回一個指向 const state_machine<>::state_base_type 的指針。如果是對一個最外層狀態進行調用,則該函數返回0。

State type information 狀態類型信息

To cut down on executable size some applications must be compiled with C++ RTTI turned off. This would render the ability to iterate over all active states pretty much useless if it weren't for the following two functions:
為了減少可執行程序的大小,有些應用程序必須在編譯時關閉 C++ RTTI. 這樣將導致對所有活動狀態進行迭代這一能力變得幾乎毫無用處,如果不是有以下兩個函數:

Both return a value that is comparable via operator==() and std::less<>. This alone would be enough to implement the DisplayStateConfiguration function above without the help of typeid but it is still somewhat cumbersome as a map must be used to associate the type information values with the state names.
這兩個函數均返回一個可以通過 operator==()std::less<> 進行比較的值。它們足以實現前面的 DisplayStateConfiguration 函數而不需要 typeid 的幫助,不過它還是有點麻煩的,因為必須使用一個 map 來將類型信息和狀態名對應起來。

Custom state type information 定制化狀態類型信息

That's why the following functions are also provided (only available when BOOST_STATECHART_USE_NATIVE_RTTI is not defined):
這就是為什麼要提供以下函數的原因(僅當定 義 BOOST_STATECHART_USE_NATIVE_RTTI 時有效):

These allow us to directly associate arbitrary state type information with each state ...
這允許我們直接將任意的狀態類型信息與各個狀態對應起來 ...

// ...

int main()
{
NumLockOn::custom_static_type_ptr( "NumLockOn" );
NumLockOff::custom_static_type_ptr( "NumLockOff" );
CapsLockOn::custom_static_type_ptr( "CapsLockOn" );
CapsLockOff::custom_static_type_ptr( "CapsLockOff" );
ScrollLockOn::custom_static_type_ptr( "ScrollLockOn" );
ScrollLockOff::custom_static_type_ptr( "ScrollLockOff" );

// ...
}

... and rewrite the display function as follows:
... 下面重寫了前面的顯示函數:

void DisplayStateConfiguration( const Keyboard & kbd )
{
char region = 'a';

for (
Keyboard::state_iterator pLeafState = kbd.state_begin();
pLeafState != kbd.state_end(); ++pLeafState )
{
std::cout << "Orthogonal region " << region << ": ";
std::cout <<
pLeafState->custom_dynamic_type_ptr< char >() << "\n";
++region;
}
}

Exception handling 異常處理

Exceptions can be propagated from all user code except from state destructors. Out of the box, the state machine framework is configured for simple exception handling and does not catch any of these exceptions, so they are immediately propagated to the state machine client. A scope guard inside the state_machine<> ensures that all state objects are destructed before the exception is caught by the client. The scope guard does not attempt to call any exit functions (see Two stage exit below) that states might define as these could themselves throw other exceptions which would mask the original exception. Consequently, if a state machine should do something more sensible when exceptions are thrown, it has to catch them before they are propagated into the Boost.Statechart framework. This exception handling scheme is often appropriate but it can lead to considerable code duplication in state machines where many actions can trigger exceptions that need to be handled inside the state machine (see Error handling in the Rationale).
異常可以從除了狀態的析構函數以外的所有用戶代碼中拋出。缺省情況下,狀態機的框架被配置為僅對異常進行簡單的處理,並不捕獲所有異常,因此這些異常會立 即傳播到狀態機的客戶代碼處。在 state_machine<> 內的作用域保護確保所有狀態對像在異常被客戶代碼捕獲前均已析構。作用域保護不會試圖調用任何 exit 函數(請見後面的 兩階段退出), 狀態可以定義為拋出自己的異常來掩蓋原來的異常。因此,如果一個狀態機想在拋出異常時做一些更有意義的事情,它必須在異常被傳播到 Boost.Statechart 框架之前將它們捕獲。這個異常處理機制通常是合適的,但是對於那些有許多動作可以觸發需要在狀態機內部處理的異常的狀態機來說,這會導致相當多的代碼重複 (請見"原理"中的 錯誤處理)。
That's why exception handling can be customized through the ExceptionTranslator parameter of the state_machine class template. Since the out-of-the box behavior is to not translate any exceptions, the default argument for this parameter is null_exception_translator. A state_machine<> subtype can be configured for advanced exception handling by specifying the library-supplied exception_translator<> instead. This way, the following happens when an exception is propagated from user code: 
這正是為什麼可以通過 state_machine 類模板的 ExceptionTranslator 參數對異常處理進行定制的原因。由於缺省的行為是對 任何異常進行轉換,所以這個參數的缺省值是 null_exception_translatorstate_machine<> 的子類可以通過指定 exception_translator<> 來配置為高級的異常處理。這種情況下,當用戶代碼傳出異常時,將發生以下動作:

  1. The exception is caught inside the framework
  2. In the catch block, an exception_thrown event is allocated on the stack
  3. Also in the catch block, an immediate dispatch of the exception_thrown event is attempted. That is, possibly remaining events in the queue are dispatched only after the exception has been handled successfully
  4. If the exception was handled successfully, the state machine returns to the client normally. If the exception could not be handled successfully, the original exception is rethrown so that the client of the state machine can handle the exception
  1. 異常在框架內部被捕獲
  2. 在 catch 塊中,一個 exception_thrown 事件被分配在棧上
  3. 還是在 catch 塊中,立 即嘗試分派 exception_thrown 事件。即,在事件隊列中剩餘的事件只能在異常處理成功後再分派
  4. 如果異常處理成功,狀態機將正常返回客戶代碼處。如果異常不能處理成功,則原來的異常被重新拋出,狀態機的客戶可以繼續處理該異 常

On platforms with buggy exception handling implementations users would probably want to implement their own model of the ExceptionTranslator concept (see also Discriminating exceptions).
在缺陷較多的平台上,要實現異常處理的用戶可能希望實現他們自己的 ExceptionTranslator 概念 (請見 可 識別的異常)。

Successful exception handling 成功的異常處理

An exception is considered handled successfully, if:
一個異常被認為處理成功,如果:

The second condition is important for scenarios 2 and 3 in the next section. In these scenarios, the state machine is in the middle of a transition when the exception is handled. The machine would be left in an invalid state, should the reaction simply discard the event without doing anything else. exception_translator<> simply rethrows the original exception if the exception handling was unsuccessful. Just as with simple exception handling, in this case a scope guard inside the state_machine<> ensures that all state objects are destructed before the exception is caught by the client.
對於下一節中的第2和第3種情形,上述第二個條件非常重要。在這些情形下,狀態機在處理異常時正處於轉換動作之中。狀態機將可能處於一個無效的狀態,如果 對異常事件的反應只是忽略事件而不做任何事。如果異常的異常未能成功,exception_translator<> 將只是重新拋出原來的異常。和簡單的異常處理一樣,這種情況下 state_machine<> 內部的作用域保護將確保在異常被客戶捕獲之前,所有狀態對像均被析構。

Which states can react to an exception_thrown event? 哪個狀態可以對 exception_thrown 事件作出反應?

Short answer: If the state machine is stable when the exception is thrown, the state that caused the exception is first tried for a reaction. Otherwise the outermost unstable state is first tried for a reaction.
簡短的回答:如果在異常拋出時,狀態機是穩定的,則引起異常的狀態會首先嘗試作出反應。否則,最外層的 不穩定狀態 首先嘗試作出反應。

Longer answer: There are three scenarios:
詳細的回答:存在三種情況:

  1. A react member function propagates an exception before calling any of the reaction functions or the action executed during an in-state reaction propagates an exception. The state that caused the exception is first tried for a reaction, so the following machine will transit to Defective after receiving an EvStart event:
    react 成員函數在調用任何反應函數之前傳出一個異常,或者在狀態內部反應傳出異 常期間,動作已執行。引發異常的狀態被首先嘗試作出反應,因此以下狀態機將在接收到一個 EvStart 事件時轉至 Defective:
    ThrowingInStateReaction

  2. A state entry action (constructor) propagates an exception:
    狀態進入動作(構造函數)傳出一個異常:
  3. A transition action propagates an exception: The innermost common outer state of the source and the target state is first tried for a reaction, so the following machine will transit to Defective after receiving an EvStartStop event:
    轉換動作傳出一個異常:由源狀態與目標狀態的公共外層狀態的最內層狀態首先嘗試作出反應,因此以下狀態機將在接收到 EvStartStop 事件後轉至 Defective:
    ThrowingTransitionAction

As with a normal event, the dispatch algorithm will move outward to find a reaction if the first tried state does not provide one (or if the reaction explicitly returned forward_event();). However, in contrast to normal events, it will give up once it has unsuccessfully tried an outermost state, so the following machine will not transit to Defective after receiving an EvNumLockPressed event:
和普通事件處理一樣,分派算法在第一次嘗試作出反應的狀態沒有提供反應(或者如果該反應顯式返回 forward_event();) 時,將向外層移動查找可用的反應。但是,與普通事件處理 不同的是,它在嘗試了最外層狀態失敗後就會放棄,所以以下狀態機在接收到 EvNumLockPressed 事件後會轉至 Defective:

ExceptionsAndOrthStates

Instead, the machine is terminated and the original exception rethrown.
該狀態機將會終止執行,並重新拋出原來的異常。

Discriminating exceptions 可識別的異常

Because the exception_thrown event is dispatched from within the catch block, we can rethrow and catch the exception in a custom reaction:
因為 exception_thrown 事件是從 catch 塊中被分派的,所以我們可以在一個定制的反應中重新拋出和捕獲異常:

struct Defective : sc::simple_state<
Defective, Purifier > {};

// Pretend this is a state deeply nested in the Purifier
// state machine 假設這是 Purifier 狀態機中的一個深層嵌套的狀態
struct Idle : sc::simple_state< Idle, Purifier >
{
typedef mpl::list<
sc::custom_reaction< EvStart >,
sc::custom_reaction< sc::exception_thrown >
> reactions;

sc::result react( const EvStart & )
{
throw std::runtime_error( "" );
}

sc::result react( const sc::exception_thrown & )
{
try
{
throw;
}
catch ( const std::runtime_error & )
{
// only std::runtime_errors will lead to a transition
// to Defective ...
// 只有 std::runtime_errors 會引發轉換到 Defective ...
return transit< Defective >();
}
catch ( ... )
{
// ... all other exceptions are forwarded to our outer
// state(s). The state machine is terminated and the
// exception rethrown if the outer state(s) can't
// handle it either... 所有其它異常均被前轉到我們的外層狀態。
// 如果外層狀態也不能處理該異常,則狀態機將被終止,異常被重新拋出
return forward_event();
}
// Alternatively, if we want to terminate the machine
// immediately, we can also either rethrow or throw
// a different exception. 另外,如果我們想馬上終止狀態機,也
// 可以重拋出或拋出一個不同的異常。
}
};

Unfortunately, this idiom (using throw; inside a try block nested inside a catch block) does not work on at least one very popular compiler. If you have to use one of these platforms, you can pass a customized exception translator class to the state_machine class template. This will allow you to generate different events depending on the type of the exception.
不幸的是,這種用法(在嵌套在一個
catch 塊中的 try 塊裡使用 throw;)至少在一個常用的編譯器上不能使用。如果你必須使用 這個平台,你可以將一個定制的異常轉譯類傳給 state_machine 類模板。這樣可以讓你根據異常的類型生成不同的事件。

Two stage exit 兩階段退出

If a simple_state<> or state<> subtype declares a public member function with the signature void exit() then this function is called just before the state object is destructed. As explained under Error handling in the Rationale, this is useful for two things that would otherwise be difficult or cumbersome to achieve with destructors only:
如果一個 simple_state<>state<> 子類聲明了一個簽名為 void exit() 的公有成員函數,則該函數會在狀態對像被析構前被調用。正如在"原理"中的 錯誤處理 一節中所解釋的,它對於以下兩件事情非常有用,沒有它的話,就只能用析構函數來處理,這既困難又麻煩:

  1. To signal a failure in an exit action
  2. To execute certain exit actions only during a transition or a termination but not when the state machine object is destructed
  1. 在退出動作中發出失敗信號
  2. 在 轉換或終止時執行特定的退出動作,這些動作不在狀態機對像析構時執行

A few points to consider before employing exit():
在使用 exit() 之前要考慮以下幾點:

Submachines & parameterized states 子狀態機和參數化狀態

Submachines are to event-driven programming what functions are to procedural programming, reusable building blocks implementing often needed functionality. The associated UML notation is not entirely clear to me. It seems to be severely limited (e.g. the same submachine cannot appear in different orthogonal regions) and does not seem to account for obvious stuff like e.g. parameters.
子狀態機是事件驅動式編程,而函數則是過程式編程,可重用構建塊的實現通常都需要功能性。相關的UML符號對我來說不夠清晰。它好像有很多局限(如中:同一個子狀態機不能出現在不同的正交區域中)且不能使用象參數這樣明顯的材料。

Boost.Statechart is completely unaware of submachines but they can be implemented quite nicely with templates. Here, a submachine is used to improve the copy-paste implementation of the keyboard machine discussed under Orthogonal states:
Boost.Statechart 完全不知道子狀態機,但是它們可以用模板很漂亮地實現。下面,我們用一個子狀態機來改進在 正交狀態 中以"複製-粘貼"方式實現的鍵盤狀態機:

enum LockType
{
NUM_LOCK,
CAPS_LOCK,
SCROLL_LOCK
};

template< LockType lockType >
struct Off;
struct Active : sc::simple_state<
Active, Keyboard, mpl::list<
Off< NUM_LOCK >, Off< CAPS_LOCK >, Off< SCROLL_LOCK > > > {};

template< LockType lockType >
struct EvPressed : sc::event< EvPressed< lockType > > {};

template< LockType lockType >
struct On : sc::simple_state<
On< lockType >, Active::orthogonal< lockType > >
{
typedef sc::transition<
EvPressed< lockType >, Off< lockType > > reactions;
};

template< LockType lockType >
struct Off : sc::simple_state<
Off< lockType >, Active::orthogonal< lockType > >
{
typedef sc::transition<
EvPressed< lockType >, On< lockType > > reactions;
};

Asynchronous state machines 異步狀態機

Why asynchronous state machines are necessary 為何需要異步狀態機

As the name suggests, a synchronous state machine processes each event synchronously. This behavior is implemented by the state_machine class template, whose process_event function only returns after having executed all reactions (including the ones provoked by internal events that actions might have posted). This function is strictly non-reentrant (just like all other member functions, so state_machine<> is not thread-safe). This makes it difficult for two state_machine<> subtype objects to communicate via events in a bi-directional fashion correctly, even in a single-threaded program. For example, state machine A is in the middle of processing an external event. Inside an action, it decides to send a new event to state machine B (by calling B::process_event()). It then "waits" for B to send back an answer via a boost::function<>-like call-back, which references A::process_event() and was passed as a data member of the event. However, while A is "waiting" for B to send back an event, A::process_event() has not yet returned from processing the external event and as soon as B answers via the call-back, A::process_event() is unavoidably reentered. This all really happens in a single thread, that's why "wait" is in quotes.
顧名思義,同步狀態機以同步方式處理每個事件。該行為通過 state_machine 類模板來實現,這個類模板的 process_event 函數在執行完所有反應(包括那些由內部事件引發的動作)後才會返回。該函數是不可重入的(其它所有成員函數也是如此,所以 state_machine<> 不是線程安全的)。這樣,要讓兩個 state_machine<> 子類型的對象通過事件來進行正確的雙向通信是很難的,即使是在一個單線程程序中。例如,狀態機 A 正處於一個外部事件的處理中。在某個動作內部,它決定發送一個新的事件給狀態機 B (通過調用 B::process_event())。然後它將"等待" B 通過一個類似於 boost::function<> 的回調發回結果,該回調函數引用 A::process_event() 將被作為事件的一個數據成員被傳遞。但是,A 正在"等待" B 發回一個事件,A::process_event() 尚未從外部事件的處理中返回,而一旦 B 通過回調函數進行回答時,A::process_event()必然要被重入。這一切在單線程中都是真實發生的,所以我們說的"等待"是用引號引起來的。

How it works 它是如何工作的

The asynchronous_state_machine class template has none of the member functions the state_machine class template has. Moreover, asynchronous_state_machine<> subtype objects cannot even be created or destroyed directly. Instead, all these operations must be performed through the Scheduler object each asynchronous state machine is associated with. All these Scheduler member functions only push an appropriate item into the schedulers' queue and then return immediately. A dedicated thread will later pop the items out of the queue to have them processed.
asynchronous_state_machine 類模板的成員函數與 state_machine 完全不同。此外, asynchronous_state_machine<> 子類型的對象不能直接創建或銷毀。相反,所有這些操作必須通過與每一個異步狀態機相關聯的 Scheduler 對像來執行。所有這些 Scheduler 成員函數只是將一個適當的項推入調度器的隊列然後立即返回。一個專用線程稍後會從隊列中取出這些項並處理它們。

Applications will usually first create a fifo_scheduler<> object and then call fifo_scheduler<>::create_processor<>() and fifo_scheduler<>::initiate_processor() to schedule the creation and initiation of one or more asynchronous_state_machine<> subtype objects. Finally, fifo_scheduler<>::operator()() is either called directly to let the machine(s) run in the current thread, or, a boost::function<> object referencing operator()() is passed to a new boost::thread. Alternatively, the latter could also be done right after constructing the fifo_scheduler<> object. In the following code, we are running one state machine in a new boost::thread and the other in the main thread (see the PingPong example for the full source code):
應用程序通常首先創建一個 fifo_scheduler<> 對象,然後調用 fifo_scheduler<>::create_processor<>()fifo_scheduler<>::initiate_processor() 來調度一個或多外 asynchronous_state_machine<> 子類型對象的創建和初始化。最後,或者直接調用 fifo_scheduler<>::operator()(),讓狀態機在當前線程中運行,或者一個引向 operator()()boost::function<> 對像被傳遞給一個新的 boost::thread. 兩者相比,後者也可以在 fifo_scheduler<> 對像構造後正確使用。在以下代碼中,我們在一個新的 boost::thread 運行一個狀態機,且在主線程中運行另一個(完整的源碼請見 PingPong 例子):

struct Waiting;
struct Player :
sc::asynchronous_state_machine< Player, Waiting >
{
// ...
};

// ...

int main()
{
// Create two schedulers that will wait for new events
// when their event queue runs empty 創建兩個調度器,它們
// 在其事件隊列為空時等待新的事件
sc::fifo_scheduler<> scheduler1( true );
sc::fifo_scheduler<> scheduler2( true );

// Each player is serviced by its own scheduler
// 每一方由它自己的調度器進行服務
sc::fifo_scheduler<>::processor_handle player1 =
scheduler1.create_processor< Player >( /* ... */ );
scheduler1.initiate_processor( player1 );
sc::fifo_scheduler<>::processor_handle player2 =
scheduler2.create_processor< Player >( /* ... */ );
scheduler2.initiate_processor( player2 );

// the initial event that will start the game
// 初始事件,開始遊戲
boost::intrusive_ptr< BallReturned > pInitialBall =
new BallReturned();

// ...

scheduler2.queue_event( player2, pInitialBall );

// ...

// Up until here no state machines exist yet. They
// will be created when operator()() is called
// 到此為止還沒有狀態機存在。它們將在調用 operator()() 時創建

// Run first scheduler in a new thread
// 在一個新的線程中運行第一個調度器
boost::thread otherThread( boost::bind(
&sc::fifo_scheduler<>::operator(), &scheduler1, 0 ) );
scheduler2(); // Run second scheduler in this thread
otherThread.join();

return 0;
}

We could just as well use two boost::threads:
我們也可以使用兩個 boost::threads:

int main()
{
// ...

boost::thread thread1( boost::bind(
&sc::fifo_scheduler<>::operator(), &scheduler1, 0 ) );
boost::thread thread2( boost::bind(
&sc::fifo_scheduler<>::operator(), &scheduler2, 0 ) );

// do something else ... 做一些其它事情

thread1.join();
thread2.join();

return 0;
}

Or, run both machines in the same thread:
或者,在同一個線程中運行兩個狀態機:

int main()
{
sc::fifo_scheduler<> scheduler1( true );

sc::fifo_scheduler<>::processor_handle player1 =
scheduler1.create_processor< Player >( /* ... */ );
sc::fifo_scheduler<>::processor_handle player2 =
scheduler1.create_processor< Player >( /* ... */ );

// ...

scheduler1();

return 0;
}

In all the examples above, fifo_scheduler<>::operator()() waits on an empty event queue and will only return after a call to fifo_scheduler<>::terminate(). The Player state machine calls this function on its scheduler object right before terminating.
在以上所有例子中,fifo_scheduler<>::operator()() 在一個空的事件隊列上等待,並且只在調用 fifo_scheduler<>::terminate() 後返回。Player 狀態機在終止前在其調度器對像上調用這一函數。


Valid HTML 4.01 Transitional

Revised 03 December, 2006

Copyright © 2003-2006 Andreas Huber Dönni

Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)