当前位置: 首页 > 编程日记 > 正文

使用C++实现一套简单的状态机模型——原理解析

在上一文中,我们介绍了该状态机模型的使用方法。通过例子,我们发现可以使用该模型快速构建满足基本业务需求的状态机。本文我们将解析该模型的基础代码,以便大家可以根据自己状态机特点进行修改。(转载请指明出于breaksoftware的csdn博客)

该模板库的基础方法实现在之后给出的工程的AutoStateChart.h中,该文件一共215行,其中有16行是辅助调试代码。以上一文中状态机类为例:

class CMachine_Download_Run_App :public AutoStateChart::CAutoStateChartMachine<CMachine_Download_Run_App, CStoreofMachine>

CMachine_Download_Run_App类继承于模板类CAutoStateChartMachine,该模板类有两个参数:继承类自身和CStoreofMachine。CStoreofMachine类顾名思义,其是状态机中用于存储数据的类。为什么要设计这样的类?因为在我们的状态机模型中,每个基础状态都是割裂的。这样设计的一个好处便是我们可以让每个基础状态的逻辑代码独立,和其他模块没有任何耦合。当我们要删除某个状态时,我们只要将它从状态机的跳转声明中摘除即可。当我们要新增某个状态时,我们也只要在状态机跳转中做相应声明即可。但是往往优点也伴随着缺点:它使得每个基础状态类的数据交互产生了障碍。特别是没有上下文关系的基础状态,跳跃性的传递信息将变得非常困难。于是我们就需要一个存活于整个状态机声明周期的“数据库”,它可以被每个基础状态类访问和修改。于是CStoreofMachine就应运而生。因为该类比较独立,所以我们先从该类开始解析。首先我们看下该类的声明:

#pragma once
#include "AutoStateChart.h"#define PROPERTY(type,name)													\
public:																		\void Set##name(const type& n) {										    \m_##name = n;														\}																		\type Get##name() {														\return m_##name;													\}																		\__declspec(property(get = Get##name, put = Set##name)) type Prop##name;	\
private:																	\type m_##name;															\class CStoreofMachine{PROPERTY(std::string, ValueString);PROPERTY(std::wstring, ValueWString);PROPERTY(int, ValueInt);
};

该类的写法可能只适合于windows的vs平台,其他平台没论证过。其实它的内容是非常简单的,就是暴露成员变量的set和get方法。只是我觉得这种写法比较有意思,才在这儿罗列下。
        我们再看下该类在模板中的使用,我们先从最基础的类开始解析

	class CEmpytLocalStore{};template<class Store = CEmpytLocalStore>class CLocalStoreAccess{public:typedef boost::function< Store& () > func;Store& GetStore(){return m_pFunc();};void SetStore(func& pCallback){m_pFunc = pCallback;};public:func m_pFunc;};

我们先定义了一个空类——CEmptyLocalStore,它相当于一个默认的“数据库”。当模板的使用者不需要“数据库”时,就可以在模板中不声明“数据库”类,此时我们的CEmptyLocalStore就生效了。比如我们上例的状态机可以改成:

class CMachine_Download_Run_App :public AutoStateChart::CAutoStateChartMachine<CMachine_Download_Run_App>

CLocalStoreAccess类主要提供如下作用:

  1. 设置访问“数据库”类对象的方法——SetStore
  2. 获取“数据库”类对象——GetStore

成员变量m_pFunc是一个函数指针,用于获取“数据库”类对象。该变量将由CLoaclStoreAccess继承类设置,相当于CLocalStoreAccess暴露了设置访问“数据库”类对象的能力。而它并不保存“数据库”类对象——它只提供“访问”能力,而不提供“存储”能力。

	template<class Store = CEmpytLocalStore>class CLocalStoreBase:public boost::enable_shared_from_this<CLocalStoreBase<Store>>,public CLocalStoreAccess<Store> {public:void Init(){ func pfunc = boost::bind(&CLocalStoreBase<Store>::_GetStore, shared_from_this()); SetStore(pfunc);};private:Store& _GetStore(){return m_Store;};private:Store m_Store;};

CLoaclStoreBase类的私有成员变量m_Store就是“数据库”类对象,即该类提供了“存储”功能。它继承于CLoaclStoreAccess类,使得该类具备了访问数据库的能力——虽然它的私有方法可以访问“数据库”类对象,但是我还是希望将这些能力分开。因为之后介绍的基础状态类要有“访问”的能力,而不应该具备“存储”的能力。如果不将这些能力进行拆分,将会导致层次结构混乱。
        CLoaclStoreBase类的init方法,打通了和ClocalStoreAccess的关系——设置函数指针。
        介绍完用于存储上下文的模板类后,我们现在可以关注下状态机相关的类了。我们先看上一文中一个基础状态类的例子

class CSimpleState_Download_From_A :public AutoStateChart::CAutoStateChartBase<CSimpleState_Download_From_A, CMachine_Download_Run_App, CStoreofMachine>

CSimpleState_Download_From_A类继承于CAutoStateChartBase模板类。第一个模板参数是继承类自身,第二个是它所属的状态机,第三个是“数据库”类。我们在看下CAutoStateChartBase类的声明

	template<class T, class MachineOrCompositeStates, class Store = CEmpytLocalStore>class CAutoStateChartBase:public boost::enable_shared_from_this<CAutoStateChartBase<T,MachineOrCompositeStates,Store>>,public CLocalStoreAccess<Store>{BOOST_TYPEOF_REGISTER_TYPE(T)public:std::string GetCurrentState(){ return typeid(T).name();};bool IsCompositeStates(){return false;};void SetInitState( const std::string& strState ){};public:virtual void Entry(){}; virtual std::string Exit(){return "";};};

该模板类使用第一个模板参数类的类名作为其继承类的状态,并使用GetCurrentState方法提供获取功能。比如上例中的状态名为class CSimpleState_Download_From_A。这个模板类继承于CLocalStoreAccess模板类,使得继承类具有可以“访问”第三个模板参数类——“数据库”类的能力——不具备“存储”能力。同时该类还暴露了两个方法——Entry和Exit,他们分别用于在进出该状态时,让状态机调用。
        状态和存储类都介绍完了,我们就剩下调度状态变化的状态机类和复合状态类。其实从某种程度上说,复合状态是一种简单的状态机,它们在很多地方存在共性。我们从状态机类入口,进行讲解。首先看下上一文中的例子

class CMachine_Download_Run_App :public AutoStateChart::CAutoStateChartMachine<CMachine_Download_Run_App, CStoreofMachine>

状态机类需要继承于CAutoStateChartMachine模板类,该类声明如下:

	template<class T, class LocalStore = CEmpytLocalStore>class CAutoStateChartMachine:public boost::enable_shared_from_this<CAutoStateChartMachine<T,LocalStore>>,public CLocalStoreAccess<LocalStore>{public:typedef LocalStore SelfStore;typedef T Self;public:CAutoStateChartMachine(){m_spStore.reset();};virtual ~CAutoStateChartMachine(){};private:virtual bool Transition(){return false;};public:void StartMachine() {if ( !m_spStore ) {m_spStore = boost::make_shared<CLocalStoreBase<LocalStore>>();m_spStore->Init();SetStore( m_spStore->m_pFunc );}while( Transition()){};};private:void Init(){};public:bool IsCompositeStates(){return false;};protected:std::string m_strCurrentState;std::string m_strCondition;MapString m_MapCompositeStatesSubState;boost::shared_ptr<CLocalStoreBase<LocalStore>> m_spStore;};

我们先看下这个类的成员变量。m_strCurrentState保存了状态机在跳转中的当前状态,m_strCondition保存了状态机中当前状态之前的状态的输出,它用于决定状态跳转方向。m_MapCompositeStatesSubState用于保存状态机中离开复合状态时的最后状态,即它记录复合状态机的浅历史。m_spStore指向“数据库”类对象。
        该模板类最重要的函数就是StartMachine,它在第一次被调用时创建了“数据库”类对象。然后死循环调用Transition方法。Tansition方法是个虚方法,它是整个模型的核心。状态机类需要实现自己的Transition方法,以使得状态机可以运转起来。

我们再看下复合状态类的基础模板

	template<class T, class MachineOrCompositeStates, class LocalStore = CEmpytLocalStore>class CCompositeStates:public CAutoStateChartBase<T,MachineOrCompositeStates,LocalStore>{BOOST_TYPEOF_REGISTER_TYPE(T)public:CCompositeStates(){};~CCompositeStates(){};private:virtual bool Transition(){return false;};public:virtual void Entry(){while(Transition());};virtual std::string Exit(){return m_strCondition;};public:std::string GetCurrentState(){return m_strCurrentState;};bool IsCompositeStates(){return true;};void SetInitState( const std::string& strState ){ m_strCurrentState = strState; };protected:std::string m_strCurrentState;std::string m_strCondition;MapString m_MapCompositeStatesSubState;};

因为复合状态也是一种状态,所以它也要有Entry和Exit两种方法。而其Entry方法就是调用Transition方法。该模板类的Transition方法也是虚方法,这意味着继承于该模板类的方法也要去实现Transition。
        于是所有的重心都集中于Transition方法的实现。
        为了让代码美观,我参考了MFC中使用宏简洁代码的思路,设计了如下的宏:

#define STARTSTATE(state)														\do {																		\boost::shared_ptr<state> sp = boost::make_shared<state>();				\sp->SetStore( m_pFunc );												\if ( sp->IsCompositeStates() ) {										\std::string strState = typeid(state).name();						\BOOST_AUTO(it, m_MapCompositeStatesSubState.find(strState));		\if ( m_MapCompositeStatesSubState.end() != it ) {					\sp->SetInitState(it->second);									\if ( DEBUGFRAMEFLAG ) {											\std::string strInitState = it->second;						\std::cout<<"CompositeStates SetInitState:"<<strInitState<<std::endl;\}																\}																	\}																		\sp->Entry();															\m_strCondition = sp->Exit();											\if ( sp->IsCompositeStates() ) {										\std::string strState = typeid(state).name();						\std::string strInnerState = sp->GetCurrentState();					\m_MapCompositeStatesSubState[strState] = strInnerState;				\if ( DEBUGFRAMEFLAG ) {												\std::cout<<"CompositeStates SaveState:"<<strState<< " " << strInnerState<< std::endl;	\}																	\}																		\} while (0);																\#define	REGISTERSTATECONVERTBEGIN(startstate)									\bool Transition() {															\do {																	\if ( m_strCurrentState.empty() ) {									\m_strCurrentState = typeid(startstate).name();					\STARTSTATE(startstate);											\return true;													\}																	\}while(0);																\#define REGISTERSTATECONVERT(fromstate,condition,tostate)						\do {																	\std::string strFromState = typeid(fromstate).name();				\std::string strToState = typeid(tostate).name();					\if ( DEBUGFRAMEFLAG	) {												\std::cout<<"strFromState:"<<strFromState<<std::endl;				\std::cout<<"condition:"<<condition<<std::endl;						\std::cout<<"strToState:"<<strToState<<std::endl;					\std::cout<<"m_strCurrentState:"<<m_strCurrentState<<std::endl;		\std::cout<<"m_strCondition:"<<m_strCondition<<std::endl<<std::endl;	\}																		\if ( IsCompositeStates() ) {											\if ( strFromState != m_strCurrentState								\|| ( !m_strCondition.empty() && condition != m_strCondition ) ) { \break;														\}																	\}																		\else {																	\if ( strFromState != m_strCurrentState								\|| condition != m_strCondition ) {								\break;															\}																	\}																		\m_strCurrentState = strToState;										\STARTSTATE(tostate);												\return true;														\}while(0);																\#define REGISTERSTATECONVERTEND()												\return false;															\};	

然后复合状态类和状态机类只要使用这些宏去组织状态跳转,就可以清晰的描述过程了。

最后附上测试工程的源码。链接:http://pan.baidu.com/s/1pJ3djKZ 密码:genu

相关文章:

干货:NIST评测(SRE19)获胜团队声纹识别技术分析 | CSDN博文精选

作者 | xjdier来源 | CSDN博文精选(*点击阅读原文&#xff0c;查看作者更多精彩文章&#xff09;近日&#xff0c;NIST说话人识别技术评测 (Speaker Recognition Evaluation&#xff0c;SRE&#xff09;正式公布榜单&#xff0c;芯片初创公司清微智能和清华大学等机构组成的联队…

网络系统传输负载测试

网络系统传输负载测试 随着企业各种信息系统相继投入使用&#xff0c;生产、管理信息逐步增加&#xff0c;企业网络规模迅速扩大&#xff0c;信息城域网承受着空前的压力&#xff0c;网络带宽严重不足&#xff0c;网络系统传输丢包、设备死机情况频频发生。我们需要对网络状况做…

android圆形旋转菜单,而对于移动转换功能支持

LZ该公司最近接手一个项目&#xff0c;需要写一个圆形旋转菜单&#xff0c;和菜单之间的移动换位支持&#xff0c;我本来以为这样的demo如若互联网是非常。想想你妈妈也帮不了我&#xff0c;空旋转&#xff0c;但它不能改变位置&#xff0c;所以LZ我们只能靠自己摸索。 最后LZ参…

微信9年:张小龙指明方向,微信AI全面开放NLP能力

作者 | 夕颜责编 | 王金许出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;一年一度的微信公开课 Pro 在广州保利世贸博览馆如期举行。一大早&#xff0c;同在博览馆举办的广州年货促展会参会者&#xff0c;夹杂着参加腾讯公开课的与会者&#xff0c;让这里变得人流攒…

实现HTTP协议Get、Post和文件上传功能——使用WinHttp接口实现

在《使用WinHttp接口实现HTTP协议Get、Post和文件上传功能》一文中&#xff0c;我已经比较详细地讲解了如何使用WinHttp接口实现各种协议。在最近的代码梳理中&#xff0c;我觉得Post和文件上传模块可以得到简化&#xff0c;于是几乎重写了这两个功能的代码。因为Get、Post和文…

第一篇文章,做个纪念

第一篇文章,做个纪念,这个blog好吗?拭目以待!转载于:https://blog.51cto.com/197536/88241

Maven工程引入jar包(转)

Maven项目引入jar包的方法&#xff0c;希望能帮助有需要的朋友们 法一.手动导入&#xff1a;项目右键—>Build Path—>Configure Build Path—>选中Libraries—>点击Add External Jars—>选中已事先下好的Jar包导入即可。 法二.通过pom.xml文件的Dependencies标…

实现HTTP协议Get、Post和文件上传功能——使用libcurl接口实现

之前我们已经详细介绍了WinHttp接口如何实现Http的相关功能。本文我将主要讲解如何使用libcurl库去实现相关功能。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; libcurl在http://curl.haxx.se/libcurl/有详细的介绍&#xff0c;有兴趣的朋友可以去读下。本文…

32岁程序员,补偿N+2:“谢谢裁我,让我翻倍!” 网友:榜样!

2019年的冬天&#xff0c;“冷”的有些频繁。12月19日&#xff0c;《马蜂窝被曝裁员40% UGC模式变现难&#xff1f;》爆火&#xff0c;据悉马蜂窝将裁员40%&#xff0c;交易中心成了“重灾区”&#xff0c;赔偿N2&#xff0c;留下的除搜索推荐、内容中心等核心部门外&#xff0…

山有木兮木有枝,心悦君兮君不知

《越人歌》今夕何夕兮&#xff0c;搴舟中流。 今日何日兮&#xff0c;得与王子同舟 蒙羞被好兮&#xff0c;不訾诟耻 心几烦而不绝兮&#xff0c;得知王子 山有木兮木有枝&#xff0c;心悦君兮君不知。本是《夜宴》中的&#xff0c;"山有木兮木有枝&#xff0c;心悦君兮君…

浅析电商、社区、游戏常用的 MySQL 架构

一般、或者必须是这样、MySQL 架构一定要结合业务来分析、设计、优化 所以不管是那种架构、根据业务要求组合成符合需求的即是最好的、不能泛泛而谈 同时、也必须注意数据的安全&#xff08;如ipsec,ssh&#xff0c;vpn传输&#xff09; 常见的架构都是进行业务切…

基于Co-Attention和Co-Excitation的少样本目标检测 | NeurIPS 2019

「免费学习 60 节公开课&#xff1a;投票页面&#xff0c;点击讲师头像」作者 | VincentLee来源 | 晓飞的算法工程笔记&#xff08;ID: gh_084c810bc839&#xff09;导读&#xff1a;论文提出CoAE少样本目标检测算法&#xff0c;该算法使用non-local block来提取目标图片与查询…

服务器架设笔记——搭建用户注册和验证功能

之前介绍的Apache Httpd相关内容&#xff0c;都是些零散的知识点。而实际运用中&#xff0c;我们要根据不同的业务&#xff0c;将这些知识点连接起来以形成各种组合&#xff0c;来满足我们的需求。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 本文我将以用…

项目管理过程中应注意的问题

软件项目从角色分工方面可以划分为研发、开发和实施三类&#xff0c;每个类型的项目有各自的管理过程。下面笔者就公司实施类项目的经历&#xff0c;从项目经理的角度谈一谈实施类项目管理过程中应该注意的一些问题&#xff0c;希望大家共勉。确定项目概况俗话说&#xff1a;“…

原创jQuery移动设备弹出框插件——msgalert.js

最近开发经常会用到顶部弹出框&#xff0c;虽然有现成的&#xff08;bootstrap等&#xff09;&#xff0c;但是都很臃肿&#xff0c;对于有些时候移动端活动页面有点大材小用。所以今晚花了20分钟写了一个通用的插件,我将其命名为msgalert.js。因为定位是jQuery插件&#xff0c…

AbutionGraph:构建以知识图谱为核心的下一代数据中台

「免费学习 60 节公开课&#xff1a;投票页面&#xff0c;点击讲师头像」作者 | 图特摩斯科技创始人闭雨哲出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;前言图特摩斯科技&#xff08;Thutmose&#xff09;基于自研的图形数据库AbutionGraph&#xff08;实时多维数…

服务器架设笔记——多模块和全局数据

随着项目工程的发展&#xff0c;多模块设计和性能优化是在所难免的。本文我将基于一些现实中可能遇到的需求&#xff0c;讲解如何在Apache的Httpd插件体系中实现这些功能。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 之前我碰到两个需求&#xff1a; 需要…

JSP学习笔记(七):使用JavaBean

bean.java publicclassB1 { publicString getString() { return"content"; } }page.jsp <%B1 b1 newB1(); out.print(b1.getString());%>

折返(Reentrancy)VS线程安全(Thread safety)

在Wiki上&#xff0c;折返例如&#xff0c;下面的定义&#xff08;接&#xff09; In computing, a computer program or subroutine is called reentrant if it can be interrupted in the middle of its execution and then safely called again ("re-entered") be…

服务器架设笔记——httpd插件支持mysql字符集选择

mysql数据库默认的字符集是latin1。默认情况下&#xff0c;我们编译的httpd插件是可以正常读取该类型的数据库&#xff0c;并且不会出现乱码。但是&#xff0c;如果我们的数据库变成其他格式&#xff0c;比如UTF8&#xff0c;那么默认读取出来的数据就是乱码&#xff0c;且无论…

只需3行代码自动生成高性能模型,支持4项任务,亚马逊发布开源库AutoGluon

作者 | KYLE WIGGERS编译 | AI科技大本营&#xff08;ID:rgznai100&#xff09;构建涉及图像、文本和表格数据集的机器学习应用并不容易。它需要特征工程或使用数据领域知识来创建使AI算法起作用的特征&#xff0c;还需要进行大量数据预处理&#xff0c;以确保训练模型时不会出…

在客户端(IE中)无法登录Citrix MetaFrame server的原因

当在IE中登录服务器时如果出现这面的错误提示&#xff1a; ERROR: The Citrix MetaFrame servers cannot process your request at this time. The Citrix XML Service object was not found. [404 Not Found] 我的原因是IE使用了8080端口&#xff0c;而我的Citrix XML servic…

虚拟化--015 配置VMware View Event database失败:

015 配置VMware View Event database失败&#xff1a;参考链接http://url.cn/VTq4zN 转载于:https://blog.51cto.com/williamliuwen/1686536

以金山界面库(openkui)为例思考和分析界面库的设计和实现——代码结构(完)

三年前&#xff0c;准备将金山界面库做一个全面的剖析。后来由于种种原因&#xff0c;这个系列被中断而一直没有更新。时过境迁&#xff0c;现在在windows上从事开发的人员越来越少&#xff0c;关注这块的技术的朋友也很少了。本以为这系列也随着技术的没落而不再被人所关注&am…

一包烟钱买到电动剃须刀,小米有品告诉你什么叫性价比

男人身上长得最快的是什么&#xff1f;答案是胡须。一名健康男性的胡须每天都要生长超过 0.4mm&#xff0c;比咱们头发的生长速度还快&#xff0c;这也是男人隔三差五就要剃须的原因之一。男人的一生是与胡子战斗一生&#xff0c;也是被剃须刀拖累的一生。出差办事儿&#xff0…

Label控件属性AssociatedControlID

可以使用Label控件来标注一个HTML表单字段。Label控件拥有属性AssociatedControlID,可以设置此属性来指向表示表单字段的ASP.NET控件。 例如,代码清单2-3中的页面含有一个简单的表单,表单包含两个字段用于输入名和姓。Label控件用于标注这两个TextBox控件。 Code<% Page Lan…

2015_8_21作业——有自翻译有复制他人的英语太差

date作用&#xff1a;打印或设置系统日期和时间格式:date [OPTION]...[FORMAT]date [u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]|是或 多选一的选项 ...代表同类内容可多次选项&#xff1a;注意短选项后不可加号-d,--dateSTRING显示时间字符串但不是立即&#xff1a;如date…

WMI技术介绍和应用——接收事件

时隔两三年&#xff0c;再次更新WMI系列博文。好在功能在三年前就已经实现了&#xff0c;现在只要补充些实例即可。 之前介绍的基本都是查询静态数据&#xff0c;而本文将要介绍非常有意思的事件接收功能。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 监控…

WML标签速查手册

WML标签速查手册 结构相关标签语法及属性<wml><wml xml:lang"lang" >    content</wml> WML元素的共有属性主要有3个&#xff0c;即id、class和xml:lang属性。WML的所有元素都有两个核心属性&#xff0c;即标识(id)和类(class)属性。它们主要用…

Python 三十大实践、建议和技巧

所有参与投票的 CSDN 用户都参加抽奖活动群内公布奖项&#xff0c;还有更多福利赠送作者 | Erik-Jan van Baaren译者 | 凯隐编辑 | Jane出品 | AI科技大本营&#xff08;ID&#xff1a;rgznai100&#xff09;【导读】2020年&#xff0c;你又立了什么新的 Flag&#xff1f;新一年…