C++ Boost MetaStateMachine定义状态机超详细讲解
目录
- 一、说明
- 二、示例和代码
一、说明
Boost.MetaStateMachine 用于定义状态机。状态机通过对象的状态来描述对象。它们描述了存在哪些状态以及状态之间可能存在哪些转换。
Boost.MetaStateMachine 提供了三种不同的方式来定义状态机。创建状态机所需编写的代码取决于前端。
如果使用基本前端或函数前端,则可以用常规方式定义状态机:创建类,从 Boost.MetaStateMachine 提供的其他类派生它们,定义所需的成员变量,并编写所需的 C++自己编码。基本前端和函数前端的根本区别在于,基本前端需要函数指针,而函数前端让你使用函数对象。
第三个前端称为 eUML,它基于特定领域的语言。该前端可以通过重用 UML 状态机的定义来定义状态机。熟悉 UML 的开发人员可以将 UML 行为图中的定义复制到 C++ 代码。您不需要将 UML 定义转换为 C++ 代码。
eUML 基于您必须与此前端一起使用的一组宏。宏的优点是您不需要直接使用 Boost.MetaStateMachine 提供的许多类。您只需要知道要使用哪些宏。这意味着您不能忘记从类派生状态机,这可能发生在基本前端或函数前端。本章介绍使用 eUML 的 Boost.MetaStateMachine。
二、示例和代码
示例 68.1。使用 eUML 的简单状态机
#include <boost/msm/front/euml/euml.hpp> #include <boost/msm/front/euml/state_grammar.hpp> #include <boost/msm/back/state_machine.hpp> #include <iostream> namespace msm = boost::msm; using namespace boost::msm::front::euml; BOOST_MSM_EUML_STATE((), Off) BOOST_MSM_EUML_STATE((), On) BOOST_MSM_EUML_EVENT(press) BOOST_MSM_EUML_TRANSITION_TABLE(( Off + press == On, On + press == Off ), light_transition_table) BOOST_MSM_EUML_DECLARE_STATE_MACHINE( (light_transition_table, init_ << Off), light_state_machine) int main() { msm::back::state_machine<light_state_machine> light; std::cout << *light.current_state() << '\n'; light.process_event(press); std::cout << *light.current_state() << '\n'; light.process_event(press); std::cout << *light.current_state() << '\n'; }
示例 68.1 使用了最简单的状态机:一盏灯恰好有两种状态。它可以打开或关闭。如果它是关闭的,它可以被打开。如果已打开,则可以将其关闭。可以从每个状态切换到每个其他状态。
示例 68.1 使用 eUML 前端来描述灯的状态机。 Boost.MetaStateMachine 没有主头文件。因此,必须将需要的头文件一一包含进来。 boost/msm/front/euml/euml.hpp 和 boost/msm/front/euml/state_grammar.hpp 提供对 eUML 宏的访问。 boost/msm/back/state_machine.hpp 需要将前端的状态机链接到后端的状态机。虽然前端提供了定义状态机的各种可能性,但状态机的实际实现是在后端找到的。由于 Boost.MetaStateMachine 仅包含一个后端,因此您无需选择实现。
Boost.MetaStateMachine 中的所有定义都在命名空间 boost::msm 中。不幸的是,许多 eUML 宏并没有明确引用这个命名空间中的类。他们使用命名空间 msm 或根本不使用命名空间。这就是为什么示例 68.1 为命名空间 boost::msm 创建了一个别名,并使 boost::msm::front::euml 中的定义可用于 using 指令。否则 eUML 宏会导致编译器错误。
要使用灯的状态机,首先要定义关和开的状态。状态是用宏 BOOST_MSM_EUML_STATE 定义的,它需要状态的名称作为它的第二个参数。第一个参数描述状态。稍后你会看到这些描述是什么样子的。示例 68.1 中定义的两个状态称为关闭和打开。
要在状态之间切换,需要事件。事件是用宏 BOOST_MSM_EUML_EVENT 定义的,它期望事件的名称作为其唯一参数。示例 68.1 定义了一个名为 press 的事件,它表示按下电灯开关的动作。由于同一事件会打开和关闭一盏灯,因此只定义了一个事件。
定义所需的状态和事件后,宏 BOOST_MSM_EUML_TRANSITION_TABLE 用于创建转换表。该表定义了状态之间的有效转换以及哪些事件触发了哪些状态转换。
BOOST_MSM_EUML_TRANSITION_TABLE 需要两个参数。第一个参数定义了转换表,第二个是转换表的名称。第一个参数的语法旨在使识别状态和事件如何相互关联变得容易。例如,Off + press == On 表示处于 Off 状态的机器通过按下事件切换到 On 状态。转换表定义的直观和不言自明的语法是 eUML 前端的优势之一。
创建转换表后,使用宏 BOOST_MSM_EUML_DECLARE_STATE_MACHINE 定义状态机。第二个参数也是更简单的一个:它设置状态机的名称。示例 68.1 中的状态机名为 light_state_machine。
BOOST_MSM_EUML_DECLARE_STATE_MACHINE 的第一个参数是一个元组。第一个值是转换表的名称。第二个值是一个使用 init_ 的表达式,它是 Boost.MetaStateMachine 提供的一个属性。稍后您将了解有关属性的更多信息。需要表达式 init_ << Off 将状态机的初始状态设置为 Off。
用 BOOST_MSM_EUML_DECLARE_STATE_MACHINE 定义的状态机 light_state_machine 是一个类。您使用此类从后端实例化状态机。在示例 68.1 中,这是通过将 light_state_machine 作为参数传递给类模板 boost::msm::back::state_machine 来完成的。这会创建一个名为 light 的状态机。
状态机提供一个成员函数 process_event() 来处理事件。如果您将事件传递给 process_event(),状态机会根据其转换表更改其状态。
为了更容易看到多次调用 process_event() 时示例 68.1 中发生的情况,调用了 current_state()。此成员函数应仅用于调试目的。它返回一个指向 int 的指针。每个状态都是一个 int 值,按照状态在 BOOST_MSM_EUML_TRANSITION_TABLE 中被访问的顺序分配。在示例 68.1 中,Off 被赋予值 0,而 On 被赋予值 1。该示例将 0、1 和 0 写入标准输出。按下灯开关两次,可以打开和关闭灯。
示例 68.2。具有状态、事件和动作的状态机
#include <boost/msm/front/euml/euml.hpp> #include <boost/msm/front/euml/state_grammar.hpp> #include <boost/msm/back/state_machine.hpp> #include <iostream> namespace msm = boost::msm; using namespace boost::msm::front::euml; BOOST_MSM_EUML_STATE((), Off) BOOST_MSM_EUML_STATE((), On) BOOST_MSM_EUML_EVENT(press) BOOST_MSM_EUML_ACTION(switch_light) { template <class Event, class Fsm> void operator()(const Event &ev, Fsm &fsm, BOOST_MSM_EUML_STATE_NAME(Off) &sourceState, BOOST_MSM_EUML_STATE_NAME(On) &targetState) const { std::cout << "Switching on\n"; } template <class Event, class Fsm> void operator()(const Event &ev, Fsm &fsm, decltype(On) &sourceState, decltype(Off) &targetState) const { std::cout << "Switching off\n"; } }; BOOST_MSM_EUML_TRANSITION_TABLE(( Off + press / switch_light == On, On + press / switch_light == Off ), light_transition_table) BOOST_MSM_EUML_DECLARE_STATE_MACHINE( (light_transition_table, init_ << Off), light_state_machine) int main() { msm::back::state_machine<light_state_machine> light; light.process_event(press); light.process_event(press); }
示例 68.2 通过一个动作扩展了灯的状态机。动作由触发状态转换的事件执行。因为动作是可选的,所以可以在没有它们的情况下定义状态机。
操作是使用 BOOST_MSM_EUML_ACTION 定义的。严格来说,定义了一个函数对象。您必须重载运算符 operator()。操作员必须接受四个参数。参数引用一个事件、一个状态机和两个状态。您可以自由定义模板或为所有参数使用具体类型。在示例 68.2 中,仅为最后两个参数设置具体类型。因为这些参数描述了开始和结束状态,所以您可以重载 operator() 以便为不同的开关执行不同的成员函数。
请注意状态 On 和 Off 是对象。 Boost.MetaStateMachine 提供了一个宏 BOOST_MSM_EUML_STATE_NAME 来获取状态的类型。如果您使用 C++11,则可以使用运算符 decltype 而不是宏。
switch_light 动作已通过 BOOST_MSM_EUML_ACTION 定义,在按下灯开关时执行。转换表已相应更改。第一个转换现在是 Off + press / switch_light == On。您在事件后的斜线后传递操作。此转换意味着如果当前状态为 Off 并且事件按下发生,则调用 switch_light 的运算符 operator()。执行操作后,新状态为开启。
示例 68.2 将 Switching on 然后 Switching off 写入标准输出。
示例 68.3。具有状态、事件、守卫和动作的状态机
#include <boost/msm/front/euml/euml.hpp> #include <boost/msm/front/euml/state_grammar.hpp> #include <boost/msm/back/state_machine.hpp> #include <iostream> namespace msm = boost::msm; using namespace boost::msm::front::euml; BOOST_MSM_EUML_STATE((), Off) BOOST_MSM_EUML_STATE((), On) BOOST_MSM_EUML_EVENT(press) BOOST_MSM_EUML_ACTION(is_broken) { template <class Event, class Fsm, class Source, class Target> bool operator()(const Event &ev, Fsm &fsm, Source &src, Target &trg) const { return true; } }; BOOST_MSM_EUML_ACTION(switch_light) { template <class Event, class Fsm, class Source, class Target> void operator()(const Event &ev, Fsm &fsm, Source &src, Target &trg) const { std::cout << "Switching\n"; } }; BOOST_MSM_EUML_TRANSITION_TABLE(( Off + press [!is_broken] / switch_light == On, On + press / switch_light == Off ), light_transition_table) BOOST_MSM_EUML_DECLARE_STATE_MACHINE( (light_transition_table, init_ << Off), light_state_machine) int main() { msm::back::state_machine<light_state_machine> light; light.process_event(press); light.process_event(press); }
示例 68.3 在转换表中使用了一个守卫。第一个转换的定义是 Off + press [!is_broken] / switch_light == On。在括号中传递 is_broken 意味着状态机在调用动作 switch_light 之前检查是否可能发生转换。这叫做守卫。守卫必须返回布尔类型的结果。
像 is_broken 这样的守卫是用 BOOST_MSM_EUML_ACTION 定义的,其方式与动作相同。因此,必须为相同的四个参数重载运算符 operator()。 operator() 必须有一个 bool 类型的返回值才能用作守卫。
请注意,您可以使用运算符等逻辑运算符!在括号内的守卫上。
如果运行该示例,您会注意到没有任何内容写入标准输出。 switch_light 动作未执行 - 灯保持关闭状态。守卫 is_broken 返回 true。但是,因为运算符运算符!使用时,括号中的表达式的计算结果为 false。
您可以使用守卫来检查是否可以发生状态转换。示例 68.3 使用 is_broken 检查灯是否坏了。虽然从关闭到打开的转换通常是可能的,并且转换表正确地描述了灯,但在此示例中,灯无法打开。尽管调用了两次 process_event(),但灯的状态为关闭。
示例 68.4。具有状态、事件、进入动作和退出动作的状态机
#include <boost/msm/front/euml/euml.hpp> #include <boost/msm/front/euml/state_grammar.hpp> #include <boost/msm/back/state_machine.hpp> #include <iostream> namespace msm = boost::msm; using namespace boost::msm::front::euml; BOOST_MSM_EUML_ACTION(state_entry) { template <class Event, class Fsm, class State> void operator()(const Event &ev, Fsm &fsm, State &state) const { std::cout << "Entering\n"; } }; BOOST_MSM_EUML_ACTION(state_exit) { template <class Event, class Fsm, class State> void operator()(const Event &ev, Fsm &fsm, State &state) const { std::cout << "Exiting\n"; } }; BOOST_MSM_EUML_STATE((state_entry, state_exit), Off) BOOST_MSM_EUML_STATE((state_entry, state_exit), On) BOOST_MSM_EUML_EVENT(press) BOOST_MSM_EUML_TRANSITION_TABLE(( Off + press == On, On + press == Off ), light_transition_table) BOOST_MSM_EUML_DECLARE_STATE_MACHINE( (light_transition_table, init_ << Off), light_state_machine) int main() { msm::back::state_machine<light_state_machine> light; light.process_event(press); light.process_event(press); }
在示例 68.4 中,传递给 BOOST_MSM_EUML_STATE 的第一个参数是一个由 state_entry 和 state_exit 组成的元组。 state_entry 是进入动作,state_exit 是退出动作。这些动作在进入或退出状态时执行。
与操作一样,进入和退出操作是使用 BOOST_MSM_EUML_ACTION 定义的。但是,重载运算符 operator() 只需要三个参数:对事件的引用、状态机和状态。状态之间的转换对于进入和退出操作无关紧要,因此只需将一个状态传递给 operator()。对于进入动作,进入该状态。对于退出操作,此状态已退出。
在示例 68.4 中,状态 Off 和 On 都有进入和退出操作。因为事件按下发生了两次,所以 Entering 和 Exiting 显示了两次。请注意,Exiting 会先显示,Entering 会显示在后面,因为执行的第一个操作是退出操作。
第一个事件按下触发从关闭到开启的转换,退出和进入各显示一次。第二次事件按下将状态切换为关闭。 Exiting 和 Entering 再次显示一次。因此,状态转换首先执行退出动作,然后执行新状态的进入动作。
示例 68.5。状态机中的属性
#include <boost/msm/front/euml/euml.hpp> #include <boost/msm/front/euml/state_grammar.hpp> #include <boost/msm/back/state_machine.hpp> #include <iostream> namespace msm = boost::msm; using namespace boost::msm::front::euml; BOOST_MSM_EUML_DECLARE_ATTRIBUTE(int, switched_on) BOOST_MSM_EUML_ACTION(state_entry) { template <class Event, class Fsm, class State> void operator()(const Event &ev, Fsm &fsm, State &state) const { std::cout << "Switched on\n"; ++fsm.get_attribute(switched_on); } }; BOOST_MSM_EUML_ACTION(is_broken) { template <class Event, class Fsm, class Source, class Target> bool operator()(const Event &ev, Fsm &fsm, Source &src, Target &trg) const { return fsm.get_attribute(switched_on) > 1; } }; BOOST_MSM_EUML_STATE((), Off) BOOST_MSM_EUML_STATE((state_entry), On) BOOST_MSM_EUML_EVENT(press) BOOST_MSM_EUML_TRANSITION_TABLE(( Off + press [!is_broken] == On, On + press == Off ), light_transition_table) BOOST_MSM_EUML_DECLARE_STATE_MACHINE( (light_transition_table, init_ << Off, no_action, no_action, attributes_ << switched_on), light_state_machine) int main() { msm::back::state_machine<light_state_machine> light; light.process_event(press); light.process_event(press); light.process_event(press); light.process_event(press); light.process_event(press); }
示例 68.5 使用守卫 is_broken 检查是否可以从 Off 到 On 的状态转换。这次 is_broken 的返回值取决于电灯开关被按下的频率。可以在灯坏之前将灯打开两次。为了计算灯打开的频率,使用了一个属性。
属性是可以附加到对象的变量。它们允许您在运行时调整状态机的行为。因为诸如开灯频率之类的数据必须存储在某个地方,所以将其直接存储在状态机、状态或事件中是有意义的。
在使用属性之前,必须对其进行定义。这是通过宏 BOOST_MSM_EUML_DECLARE_ATTRIBUTE 完成的。传递给 BOOST_MSM_EUML_DECLARE_ATTRIBUTE 的第一个参数是类型,第二个是属性的名称。示例 68.5 定义了 int 类型的属性 switched_on。
定义属性后,必须将其附加到对象。该示例将属性 switched_on 附加到状态机。这是通过元组中的第五个值完成的,该值作为第一个参数传递给 BOOST_MSM_EUML_DECLARE_STATE_MACHINE。使用 attributes_,来自 Boost.MetaStateMachine 的关键字用于创建 lambda 函数。要将属性 switched_on 附加到状态机,请使用 operator<< 将 switched_on 写入 attributes_,就好像它是一个流一样。
元组中的第三个和第四个值都设置为 no_action。该属性作为元组中的第五个值传递。第三个和第四个值可用于定义状态机的进入和退出操作。如果没有定义进入和退出操作,请使用 no_action。
将属性附加到状态机后,可以使用 get_attribute() 访问它。在示例 68.5 中,此成员函数在入口操作 state_entry 中被调用以增加属性的值。因为 state_entry 仅链接到状态 On,switched_on 仅在灯打开时递增。
switched_on 也可以从守卫 is_broken 访问,它检查属性的值是否大于 1。如果是,守卫返回 true。由于属性是使用默认构造函数初始化的,并且 switched_on 设置为 0,如果灯已打开两次,is_broken 将返回 true。
在示例 68.5 中,事件按下发生了五次。灯被打开和关闭两次,然后再次打开。灯打开的前两次会显示“已打开”。但是,第三次打开灯时没有输出。发生这种情况是因为 is_broken 在灯被打开两次后返回 true,因此,没有从 Off 到 On 的状态转换。这意味着不执行状态 On 的进入操作,并且该示例不写入标准输出。
示例 68.6。访问转换表中的属性
#include <boost/msm/front/euml/euml.hpp> #include <boost/msm/front/euml/state_grammar.hpp> #include <boost/msm/back/state_machine.hpp> #include <iostream> namespace msm = boost::msm; using namespace boost::msm::front::euml; BOOST_MSM_EUML_DECLARE_ATTRIBUTE(int, switched_on) void write_message() { std::cout << "Switched on\n"; } BOOST_MSM_EUML_FUNCTION(WriteMessage_, write_message, write_message_, void, void) BOOST_MSM_EUML_STATE((), Off) BOOST_MSM_EUML_STATE((), On) BOOST_MSM_EUML_EVENT(press) BOOST_MSM_EUML_TRANSITION_TABLE(( Off + press [fsm_(switched_on) < Int_<2>()] / (++fsm_(switched_on), write_message_()) == On, On + press == Off ), light_transition_table) BOOST_MSM_EUML_DECLARE_STATE_MACHINE( (light_transition_table, init_ << Off, no_action, no_action, attributes_ << switched_on), light_state_machine) int main() { msm::back::state_machine<light_state_machine> light; light.process_event(press); light.process_event(press); light.process_event(press); light.process_event(press); light.process_event(press); }
示例 68.6 与示例 68.5 做同样的事情:将灯打开两次后,灯坏了,无法再打开。虽然前面的示例在操作中访问了 switched_on 属性,但此示例使用转换表中的属性。
Boost.MetaStateMachine 提供函数 fsm_() 来访问状态机中的属性。这样就定义了一个守卫来检查 switched_on 是否小于 2。并且定义了一个动作,每次状态从 Off 切换到 On 时都会增加 switched_on。
请注意,守卫中的小于比较是通过 Int_<2>() 完成的。数字 2 必须作为模板参数传递给 Int_ 以创建此类的实例。这将创建一个具有 Boost.MetaStateMachine 所需类型的函数对象。
示例 68.6 还使用宏 BOOST_MSM_EUML_FUNCTION 使函数成为动作。传递给 BOOST_MSM_EUML_FUNCTION 的第一个参数是可以在函数前端使用的动作的名称。第二个参数是函数的名称。第三个参数是在 eUML 中使用的操作名称。第四个和第五个参数是函数的返回值——一个用于动作用于状态转换的情况,另一个用于动作描述进入或退出动作的情况。以这种方式将 write_message() 转换为动作后,将在转换表中的 ++fsm_(switched_on) 之后创建并使用 write_message_ 类型的对象。在从 Off 到 On 的状态转换中,属性 switched_on 递增,然后调用 write_message()。
与示例 68.5 中一样,示例 68.6 显示两次打开。
Boost.MetaStateMachine 提供额外的函数,例如 state_() 和 event_(),以访问附加到其他对象的属性。其他类,例如 Char_ 和 String_,也可以像 Int_ 一样使用。
提示
正如您在示例中看到的,前端 eUML 要求您使用许多宏。头文件 boost/msm/front/euml/common.hpp 包含所有 eUML 宏的定义,这使其成为一个有用的参考。
练习
为可以关闭、打开或倾斜的窗口创建状态机。关闭的窗户可以打开或倾斜。如果不先关闭打开的窗户,则无法倾斜它。倾斜的窗户也不能在不先关闭的情况下打开。通过打开和倾斜您的窗口几次来测试您的状态机。使用 current_state() 将状态写入标准输出。
扩展状态机:窗户应该是智能家居的一部分。状态机现在应该计算窗户打开和倾斜的频率。要测试您的状态机,请打开并倾斜您的窗口几次。在程序结束时,将窗口打开的频率和倾斜的频率写入标准输出。
到此这篇关于C++ Boost MetaStateMachine定义状态机超详细讲解的文章就介绍到这了,更多相关C++ Boost MetaStateMachine内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!