Boost库中的状态机管理:BoostStateChart学习整理

本文详细介绍了如何在Boost库中使用statechart进行状态机的设计,包括状态、事件的定义,状态转移规则,以及事件处理的各种行为,如状态切换、事件队列、延迟事件和终止逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、概述

Boost中管理状态机的相关库, statechart大概可以包含三块,状态机:state_machine,状态:statesimple_state以及事件 eventstate_machine作为载体,state作为内容,event作为事件传输介质。

  • 状态:对象在其生命周期内,所处于的运动和发展阶段。例如:人可以处于工作中这个状态,也可以处于娱乐中状态。
    状态应该有上下文环境。例如:人处于工作中状态,要属于职业生涯状态而不能是退休状态,也必须是活着的,而不能是死亡的状态。状态可以有子状态。例如:工作中状态,可以有开会中,会客中,被训斥中等子状态。
  • 状态机:由许多相关状态构成的集合。把一个对象,或者一个系统看做是一个状态机。
  • 事件:状态能够接收并处理事件。事件可以改变状态,促使状态发生转移。

二、使用

1、定义状态机

struct RecoveryMachine : state_machine< RecoveryMachine, Reset > {};
这里定义了一个状态机类RecoveryMachine。初始状态在Reset

2、定义状态

在statechart里,状态的定义有两种方式:

  • 不定义子状态时:
struct Reset : boost::statechart::state< Reset, RecoveryMachine >, NamedState {};

定义一个状态需要继承boost::statechart::simple_state或者boost::statechart::state类。上面Reset状态继承了boost::statechart::state类。该类的模板参数中,第一个参数为状态机自己的名字Reset,第二个参数为所属状态机的名字,表明Reset是状态机RecoveryMachine的一个状态。

  • 定义子状态时:
struct Start;
struct Started : boost::statechart::state<Started, RecoveryMachine, Start>, NamedState {};
  • 举例:状态Started也是状态机RecoveryMachine的一个状态,模板参数中多了一个参数Start,它是状态Started的默认初始子状态,其定义如下:
struct Start : boost::statechart::state<Start, Started>, NamedState {};

上面定义的状态Start是状态Started的子状态。第一个模板参数是自己的名字,第二个模板参数是该子状态所属父状态的名字。
综上所述,一个状态,要么属于一个状态机,要么属于一个状态,成为该状态的子状态。其定义的模板参数是自己,第二个模板参数是拥有者,第三个模板参数是它的起始子状态。

3、定义事件

struct QueryState : boost::statechart::event<QueryState> { int raised_salary; }; 

QueryState为一个事件,需要继承boost::statechart::event类,模板参数为自己的名字。可以加一些成员变量。

4、事件、行为、状态切换

事件是状态机中可能被触发的消息,当事件被触发的时候,如果不采取特殊措施,当前所处状态会首先接受到事件,如果注册了事件处理函数,则交由事件处理函数进行处理,否则将向外层抛出事件,直到找到对应的消息处理函数。异常传播也是类似的机制,如果自己这层没有捕捉到异常异常就会向外层扩散,只不过是如果连最外层的状态找不到对应的事件处理函数,这个消息就失效了,但是异常会一直向外传播直到自己的客户端程序。
行为,表示如何处理事件。有一种简单的方式是收到某个消息直接进行状态切换 类似于 sc::transition<EvStartStop, MyState_0>的方式,收到EvStartStop就跳转到MyState_0状态了,比较暴力直接;还有一种方式是使用react函数进行自定义处理,如sc::custom_reaction<EvBingo>,这时候在cpp中实现一个react,比如下面这样:

//达成目标切换到初始
sc::result MyState_2_1::react(const EvBingo& event)
{
  //post_event(EvStartStop()); //允许从构造函数中发放消息
  std::cout << "恭喜你完成了状态机训练,让我们从头开始!!" << std::endl;
  std::cout << context<MyStateMachine>().rtStr() << std::endl;
  return discard_event();
}

如果是自定义了react消息,表示当前状态接受并处理了EvBingo消息,他有权抛弃事件(discard_event),抛出其他消息但是会延迟到本函数执行完毕后抛出(post_event(xxx)),立即抛出消息(process_event(xxx)),继续向上层状态抛出同一事件(forward_event),或者直接跳转 (transit),但是要注意的是,如果使用的是transit或者process_event这种可能导致状态即时切换的函数时,最好在之后的流程中不要对当前state进行操作了,因为它相当于你delete了一个类对象但是还在继续使用对象的数据,这个操作是相当危险的。所以 transitprocess_event一般是放在 react函数流程的最后执行的。
上面介绍的几个函数包含了行为切换的一部分,既可以在当前状态内自由的抛弃,传播(新)事件,甚至直接做状态切换等,这里顺便提一句,还可以把事件定义为defferal,如 sc::deferral< EvBingo >,那这个事件也会被延迟抛出,只不过它延迟的时间点更靠后,上面说到 post_event(xxx)forward_event可能会等到react函数执行完毕之后再进行事件触发,但是deferral保证只有当前状态exit之后才分发这个事件。也就是说如果没有出当前状态这个事件将被永久积压!
以上几种行为都是在某种状态中进行的,实际上无论处在react函数中,还是状态的析构函数中,都还没脱离当前状态,那么如何在状态切换的中间做一点事情,即当前状态机不处于任何状态,这就用到了 transaction function,他在本状态切换出去之后并且尚未进入下一状态之前被调用

sc::transition<EvRtToLast, sc::deep_history<MyState_2_0>, MyStateMachine, &MyStateMachine::onEvStartState1>, //中间状态

EvRtToLast事件被分发后,状态机脱离了本状态准备切换到下一状态,此时会在中间调用 MyStateMachine::onEvStartState1函数。

5、事件处理相关函数

( 1 )、状态转移
struct A1 : simple_state<A1, A> { typedef transition< Evt, B  > reactions; };

状态转移规则用typedef说明,注意reactions不能拼错,当遇到事件Evt时,就转移到状态B。状态转移能从一个状态跳转到任意的一个状态,即使状态是嵌套的深层子状态。

( 2 )、监护条件:监护条件是一个函数或函数对象,用于确定是否允许进行状态转换和状态转移的目标。
  1. typedef sc::transition<Event1, State2, GuardCondition> reactions;只有当 GuardCondition 返回 true 时,才会允许从当前状态切换到 State2
  2. 使用custom_reaction<>自定义转换机制。
struct A : sc::simple_state<A, M> {
    typedef sc::custom_reaction< Evt > reactions;
    sc::result react( const Evt &evt )
    {
        //实现Transition Action
        cout<<"Transition Action Called"<<endl;

        //实现Guard Condition
        if (evt.m_raised_salary > 0){
            return transit< C >(); //跳转到状态C    
        }
        if (evt.m_raised_salary < 0){
            return transit< B >(); //跳转到状态B    
        }
        else{
            return discard_event(); //什么也不做
        }
    }
};
(3 )、转移动作Transition Action:状态转移过程中的动作,可以用custom_reaction<>机制实现,也可以用以下方法实现。
struct A : simple_state<A, M>{ typedef transition< Evt, B,  S, &S::transtion_action  > reactions; }
//S和A,B必须有关联
struct S : sc::state_machine< S, 。。。 > {
    void transtion_action(const Evt &){ //transtion_action必须是这样的函数签名。
        std::cout<<"S::transtion_action() called"<<std::endl;
    }
};

Evt发生时,状态A转移到状态B,转移过程中调用S中的transtion_action函数。

( 4 )、投递事件
S s;
s.initiate();
s.process_event(Evt()); 

事件首先被投递给最深层的当前子状态,让后向外层投递。假设C->B->A,其中C是当前最深子状态,并且ABC三者都定义了对事件Evt的转移规则。则C先获得事件Evt的处理。一般来说B,A将不能感知到事件。C可以调用forward_event()将事件抛给B处理。这是当C不能处理一个事件X(未定义状态转移)时,默认行为(抛给直接上级状态处理)。

( 5 )、进入/退出某一状态时执行的动作(entry/exit reaction),在状态类的构造和析构函数中执行即可。
  • 用于执行与状态的进入和退出相关的操作,而在主循环中使用 process_event() 来触发其他事件和执行状态转换
  • entry reaction即为状态构造函数,不可在该函数中使用proces_event()若需要在该区域发布事件应使用post_event(),另外,虽然在custom_reactionin_state_reaction中可使用process_event,但推荐使用post_event代替。
  • 不可再该函数中使用transit(),状态正在切换时,不可强行转换状态,若需要在reaction结束后转换状态,推荐使用post_event发布一个事件,并定义相应的transition响应。
( 6 )、历史状态,包含历史状态的定义方法:
struct A : simple_state<A, M, A1, sc::has_shallow_history > {}

状态A有一个特殊的伪子状态—浅历史状态。其初始值可以认为是等于初始子状态,这里是A1。当从A转移出后,能记住最后一个直接子状态例如:A9。当从别处重新回到A状态时,能直接进入到A9。(而不是进入初始子状态。)“浅”是指:只记住A的直接子状态(A的下一个子状态);“深”是指:能记住A的最深的嵌套子状态(A的子状态的子状态)。

struct X : simple_state<X, ... >{typedef sc::transition<Evt, sc::shallow_history< A1 > > reactions;}

回到A,进入历史状态

( 7 )、定义多种事件处理规则
typedef boost::mpl::list<
        sc::transition< Evt1, B >
        sc::transition< Evt2, C >
    > reactions;

一个状态对多种事件的反应规则

( 8 )、事件队列
StateMachine sm;
sm.initiate();
// 使用 post_event() 发送事件
sm.post_event(Event1());

// ...
// 其他代码
// ...
  
// 状态机的事件处理循环
sm.process_events();

post_event()函数用于向状态机发送事件。它是在状态机中异步触发事件的一种方式。post_event() 函数不会立即触发状态转换,而是将事件放入状态机的事件队列中,等待状态机进行处理。当状态机调用 process_events() 函数时,它会按照事件的先后顺序处理事件队列中的事件,并执行相应的状态转换。

( 9 )、延迟事件
struct B : sc::simple_state<B, M> {typedef sc::deferral< EvtDefer > reactions;}

处于B状态时,暂时不能处理EvtDefer,但是希望离开B状态时,才处理事件EvtDefer。延迟事件同post_event()不同之处在于,post_event能把事件放入状态机主事件队列,在action执行完成后,就会立刻执行被post的事件(没有等待B结束,这个重要条件)

( 10 )、terminate()会造成状态机离开当前活动状态,到达伪终态。使用bool lb = s.terminated(); 其中s是一个状态机对象,可以查询状态机是否正在运行。
typedef boost::mpl::list<
     sc::transition< Evt2A,A >,
     sc::deferral< EvtDefer >,
     sc::termination< EvtKill >
  > reactions;

或者

sc::result A::react( const EvtKill & )
{
    return terminate();
}
( 11 )、事件处理结果:sc::result,枚举类型,react函数的返回值类型。
sc::result A::react( const Evt& )
{
    return discard_event(); //忽略事件
}
sc::result A::react( const Evt& )
{
    return forward_event(); //请求向上级状态处理此事件
}
sc::result A::react( const Evt& )
{
    return transit<B>(); //转移到B状态
}
sc::result A::react( const Evt& )
{
    return terminate(); //状态机自杀
}
(12)、contex<>()

用来获取一个状态的直接或间接的上下文的访问权,可以返回当前状态的上下文对象,具体对象有参数模板指定,类似于从状态机中获取一个引用或指针,以便可以通过该对象访问状态机的属性和方法,后面跟.可以调用该对象的成员。

context< StateMachine >())  //返回当前状态所属的状态机

参考资料:

https://blog.csdn.net/skdkjzz/article/details/46225789
https://www.cnblogs.com/sunbines/p/15720863.html

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值