狀態機的實現探討
(譯)狀態機的實現探討
原文連結地址:http://drdobbs.com/cpp/184401236?pgno=1
實現一個狀態機很容易,但是實現一個好的狀態機卻不簡單。一般實現狀態機的時候會有如下的實現程式碼:
switch (state_)
case A:
do_A();
case B:
do_B();
end switch
當狀態量少並且各個狀態之間變化的邏輯比較簡單時,這種方法無可厚非,但是它有如下缺點:
l 邏輯程式碼較混亂;如狀態A到狀態B的切換,如果需要驗證有效性,那麼程式碼會變得臃腫,不再那麼直觀;示例:
case A:
if (current_state != C)
return -1;
else
current_state = A;
return 0;
case ....:
....
l 難擴充套件;大部分狀態的處理是相似的,而某些特殊的狀態則要特殊處理,比如需要提供附加資料,比如在Task中設定一個狀態為suspend,那麼需要傳遞一個要掛起的時間。這種情況類似於GUI程式中的事件通知介面,如:
handle_event(EventId event_, Long ext,...)
ext實際上可以傳遞任何東西。比如觸發了一個檔案拖動到圖示的事件dropOpen,那麼可以將要open的檔案路徑的地址通過ext傳入。這種方式挺萬金油的,所以在實現狀態機的時候,完全可以借鑑一下。
Context:
假設場景如下:實現任務Task,它是一個狀態機,其狀態變化如圖:
l Task被建立後假設獲取了必須資源,進入Ready狀態
l Ready狀態可以被任務佇列執行run, 那麼Task進入Running狀態
l Ready狀態時可以被suspend掛起,掛起時需要標識掛起的時間
l Running狀態時可以被掛起
l Suspended狀態可以通過潤使Task進入running狀態
l Running、Ready、Suspended狀態都可以通過cancel,直接進入ended狀態
Question:
n 合理實現各個狀態之間的切換
n 方便擴充套件,任務狀態有可能會增加,任務的觸發時間可能會改變等,狀態機的實現必須能夠快速適應邏輯的變化
Solution:
下面探討如下的實現方案:
u 設計基類:
- 首先是用於傳遞擴充套件資料的萬金油虛類
#ifndef EVENT_DATA_H
#define EVENT_DATA_H
class EventData
{
public:
virtual ~EventData() {};
void* data() = 0;
};
#endif //EVENT_DATA_H
- 狀態的通用介面類StateMachine 介面, 此類不但定義了介面,其實其規定了狀態機實現的模板,任何狀態機的實現都可以按照此模板按部就班的實現.
#ifndef STATE_MACHINE_H
#define STATE_MACHINE_H
#include <stdio.h>
#include "EventData.h"
struct StateStruct;
// base class for state machines
class StateMachine
{
public:
StateMachine(int maxStates);
virtual ~StateMachine() {}
protected:
enum { EVENT_IGNORED = 0xFE, CANNOT_HAPPEN };
unsigned char currentState;
void ExternalEvent(unsigned char, EventData* = NULL);
void InternalEvent(unsigned char, EventData* = NULL);
virtual const StateStruct* GetStateMap() = 0;
private:
const int _maxStates;
bool _eventGenerated;
EventData* _pEventData;
void StateEngine(void);
};
typedef void (StateMachine::*StateFunc)(EventData *);
struct StateStruct
{
StateFunc pStateFunc;
};
#define BEGIN_STATE_MAP
public:
const StateStruct* GetStateMap() {
static const StateStruct StateMap[] = {
#define STATE_MAP_ENTRY(entry)
{ reinterpret_cast<StateFunc>(entry) },
#define END_STATE_MAP
{ reinterpret_cast<StateFunc>(NULL) }
};
return &StateMap[0]; }
#define BEGIN_TRANSITION_MAP
static const unsigned char TRANSITIONS[] = {
#define TRANSITION_MAP_ENTRY(entry)
entry,
#define END_TRANSITION_MAP(data)
0 };
ExternalEvent(TRANSITIONS[currentState], data);
#endif //STATE_MACHINE_H
ExternalEvent介面是帶有效性驗證的介面,他首先判斷狀態的有效性,如果有效則呼叫InternalEvent, InternalEvent是沒有驗證的內部介面,它直接的修改狀態。
- StateMachine 的實現;此實現為通用的邏輯模板,任何狀態機的實現都可以套用此模板。
#include <assert.h>
#include "StateMachine.h"
StateMachine::StateMachine(int maxStates) :
_maxStates(maxStates),
currentState(0),
_eventGenerated(false),
_pEventData(NULL)
{
}
// generates an external event. called once per external event
// to start the state machine executing
void StateMachine::ExternalEvent(unsigned char newState,
EventData* pData)
{
// if we are supposed to ignore this event
if (newState == EVENT_IGNORED) {
// just delete the event data, if any
if (pData)
delete pData;
}
else if (newState == CANNOT_HAPPEN) {
//! throw exception("xxx");
//! or
//! logerror("....");
}
else {
// generate the event and execute the state engine
InternalEvent(newState, pData);
StateEngine();
}
}
// generates an internal event. called from within a state
// function to transition to a new state
void StateMachine::InternalEvent(unsigned char newState,
EventData* pData)
{
_pEventData = pData;
_eventGenerated = true;
currentState = newState;
}
// the state engine executes the state machine states
void StateMachine::StateEngine(void)
{
EventData* pDataTemp = NULL;
if (_eventGenerated) {
pDataTemp = _pEventData; // copy of event data pointer
_pEventData = NULL; // event data used up, reset ptr
_eventGenerated = false; // event used up, reset flag
assert(currentState < _maxStates);
// execute the state passing in event data, if any
const StateStruct* pStateMap = GetStateMap();
(this->*pStateMap[currentState].pStateFunc)(pDataTemp);
// if event data was used, then delete it
if (pDataTemp) {
delete pDataTemp;
pDataTemp = NULL;
}
}
}
在這裡ExternalEvent判斷該狀態是否是有效的,如果是EVENT_IGNORED,那麼可以直接忽略此操作,如果是CANNOT_HAPPEN,說明出現了邏輯錯誤。
l 具體task的實現如下:
#ifndef TASK_H
#define TASK_H
#include "StateMachine.h"
struct TaskData : public EventData
{
int xxx;
};
class Task : public StateMachine
{
public:
Task() : StateMachine(ST_MAX_STATES) {}
// external events taken by this state machine
void Suspend();
void Run();
void Cancel();
private:
// state machine state functions
void ST_Ready();
void ST_Running();
void ST_Suspended(TaskData* pData);
void ST_Ended();
// state map to define state function order
BEGIN_STATE_MAP
STATE_MAP_ENTRY(ST_READY)
STATE_MAP_ENTRY(ST_RUNNING)
STATE_MAP_ENTRY(ST_SUSPENDED)
STATE_MAP_ENTRY(ST_ENDED)
END_STATE_MAP
// state enumeration order must match the order of state
// method entries in the state map
enum E_States {
ST_READY = 0,
ST_RUNNING,
ST_SUSPENDED,
ST_ENDED,
ST_MAX_STATES
};
};
#endif //MOTOR_H
BEGIN_STATE_MAP 巨集將自定義的狀態函式註冊到StateMap中,這樣可以直接通過state值索引得到其對應的狀態函式。
l Task的實現程式碼
#include <assert.h>
#include "task.h"
void Task::Suspend(MotorData* pData)
{
BEGIN_TRANSITION_MAP // - Current State -
TRANSITION_MAP_ENTRY (ST_Suspended) // ST_READY
TRANSITION_MAP_ENTRY (ST_Suspended) // ST_RUNNING
TRANSITION_MAP_ENTRY (EVENT_IGNORED) // ST_SUSPENDED
TRANSITION_MAP_ENTRY (CANNOT_HAPPEN) // ST_ENDED
END_TRANSITION_MAP(pData)
}
void Task::Run(void)
{
BEGIN_TRANSITION_MAP // - Current State -
TRANSITION_MAP_ENTRY (ST_RUNNING) // ST_READY
TRANSITION_MAP_ENTRY (EVENT_IGNORED) // ST_RUNNING
TRANSITION_MAP_ENTRY (ST_RUNNING) // ST_SUSPENDED
TRANSITION_MAP_ENTRY (CANNOT_HAPPEN) // ST_ENDED
END_TRANSITION_MAP(NULL)
}
void Task::Cancel(void)
{
BEGIN_TRANSITION_MAP // - Current State -
TRANSITION_MAP_ENTRY (ST_ENDED) // ST_READY
TRANSITION_MAP_ENTRY (ST_ENDED) // ST_RUNNING
TRANSITION_MAP_ENTRY (ST_ENDED) // ST_SUSPENDED
TRANSITION_MAP_ENTRY (EVENT_IGNORED) // ST_ENDED
END_TRANSITION_MAP(NULL)
}
void Task::ST_Ready()
{
InternalEvent(ST_READY);
}
void Task::ST_Running()
{
InternalEvent(ST_RUNNING);
}
void Task::ST_Suspended(MotorData* pData)
{
InternalEvent(ST_SUSPENDED, pData);
}
void Task::ST_Ended()
{
InternalEvent(ST_ENDED);
}
在狀態的處理上思路是:狀態要麼是有效的、要麼是可以忽略的、要麼是根本不會發生的。