【发布时间】:2013-11-19 14:10:20
【问题描述】:
在一个正在进行的项目中,需要实现多个有限状态机。一个公共层会很好,以便可以开发类似的检查和平衡。状态机的要求足够多样化,因此我希望在 FSM 模板之外确定数据类型,这是用户的责任。此外,我希望持久数据完全由 FSM 实现作者控制。最后,线程安全是模板中状态转换的必要条件。
这导致了我认为的精简状态转换代码。这个想法是,FSM 模板应该是专门的和,由 FSM 实现者封装到一个可用的外部类中。该包装类将是一个完整的特定 FSM 实现。 FSM 模板使用在其外部定义的数据类型和逻辑,但这些状态和数据不应该在 FSM 的最终包装器实现之外可见。
还有几个问题(按照 C++ 标准,我很环保)。他们在这里:
- 在这种情况下使用
type_index作为映射键是奇怪的还是符合type_index的设计意图?它是否足够独特? -
shared_ptr的“魔法”会自动正确地销毁包装类中实例化的所有数据,这对吗? - 此模板和实现中是否存在破坏顺序问题?
- 有没有办法测试正确的销毁及其顺序?
- 有没有一种方法(或合理的需要)来测试状态转换是否在
FSM::operator()方法中实际同步?
下面是模板本身和主要的 Turnstile 类,从the Wikipedia article 严格实现。如果需要,其他六个具有特定类型和状态逻辑的实现文件位于my GitHub FSM repository。
下面的 FSM 模板采用对消息做出反应的逻辑向量,向量的头部定义初始状态。它将每个消息参与者包装在一个状态对象中,将状态数据附加到它上面。当 FSM 模板运行一个状态时,它还可以检查返回的“下一个”状态键是否有效(未来,此处省略)。
这是模板:
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <map>
#include <typeindex>
#include <mutex>
namespace tools {
template <typename Message, typename Content>
class FSM {
public:
class React {
public:
virtual std::type_index operator()( const Message & msg, std::shared_ptr<Content> ) {
return std::type_index(typeid(this));
};
};
typedef std::vector<std::shared_ptr<React>> Flux;
FSM( Flux & flux, std::shared_ptr<Content> ctnt )
: current( std::type_index( typeid( *( flux.at(0) ) ) ) )
, content( ctnt )
{
for ( auto rct: flux ) {
states[ std::type_index( typeid( *rct ) ) ] = State( rct, content );
};
};
void operator()( const Message & msg ) {
std::lock_guard<std::mutex> lock( fsm_mutex );
current = states.at( current )( msg );
};
private:
class State {
public:
State( std::shared_ptr<React> rct, std::shared_ptr<Content> ctnt )
: content( ctnt ), react( rct ) {};
State() : content( nullptr ), react( nullptr ) {};
std::type_index operator()( const Message & message ) {
return (*react)( message, content );
}
private:
std::shared_ptr<Content> content;
std::shared_ptr<React> react;
};
std::mutex fsm_mutex;
std::shared_ptr<Content> content;
std::type_index current;
std::map<std::type_index, State> states;
};
}
实现类头:
#pragma once
#include "TurnstileTypes.h"
namespace tools {
namespace test {
namespace fsm {
// Implementation of the Turnstile FSM example from http://en.wikipedia.org/wiki/Finite_state_machine#Example:_a_turnstile
// Added RESET event to better test state.
class Turnstile {
public:
Turnstile(void);
void operator()( const TurnstileEvent & msg );
unsigned long Pushed();
unsigned long Paid();
std::string Now();
private:
TurnstileFSM::Flux flux;
std::unique_ptr<TurnstileFSM> fsm;
std::shared_ptr<TurnstileData> stats;
};
}
}
}
实现类主体:
#include "TurnstileTypes.h"
#include "TurnstileLocked.h"
#include "TurnstileUnlocked.h"
#include "Turnstile.h"
namespace tools {
namespace test {
namespace fsm {
Turnstile::Turnstile(void)
: stats( std::make_shared<TurnstileData>() )
{
flux.push_back( std::make_shared<Locked>() );
flux.push_back( std::make_shared<Unlocked>() );
fsm = std::unique_ptr<TurnstileFSM>( new TurnstileFSM( flux, stats ) );
};
void Turnstile::operator()( const TurnstileEvent & msg ) {
(*fsm)( msg );
};
unsigned long Turnstile::Pushed() { return stats->pushes; };
unsigned long Turnstile::Paid() { return stats->coins; };
std::string Turnstile::Now() { return stats->state_name; };
}
}
}
非常感谢您的回答!
[更新 131120] 提及FSM.h 并不意味着是具有“公共接口”的库,这可能会有所帮助。更重要的是,它是让 FSM 作者前进的样板代码。实际的用户 FSM 接口由该 FSM 实现定义 - 在附加的示例中,它位于 Turnstile.h 中。因此,FSM.h 模板非常简短,旨在供 FSM 作者阅读和理解。
我还希望他们(我自己现在)理解模板中记录的缺陷——例如在 State()()method 中向任何 FSM 发送消息将死锁的事实。这可能是由一系列调用引起的。 Thanks to Florian Philipp comment on Google+ 指出这一点。
还有一个意图是在 FSM 中对 Message 和 Content 类型了解最少,以便 FSM 作者在实施时具有最大的灵活性 - 包括通过访问器之类的东西从内容中获取信息。这种灵活性伴随着不改变内容的责任。
【问题讨论】:
-
你有什么理由不使用Meta State Machine ?
-
目标代码库上还不允许使用第三方库。此外,上述解决方案旨在最大限度地减少那里的重构。
-
@maxim-yegorushkin(以上评论提及)