日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

C++?Boost?MetaStateMachine定義狀態機超詳細講解_C 語言

作者:無水先生 ? 更新時間: 2023-01-03 編程語言

一、說明

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';
}

Example?68.1?

示例 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);
}

Example?68.2?

示例 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);
}

Example?68.3?

示例 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);
}

Example?68.5?

示例 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);
}

Example?68.6?

示例 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()。

Example?68.6?

與示例 68.5 中一樣,示例 68.6 顯示兩次打開。

Boost.MetaStateMachine 提供額外的函數,例如 state_() 和 event_(),以訪問附加到其他對象的屬性。其他類,例如 Char_ 和 String_,也可以像 Int_ 一樣使用。

提示

正如您在示例中看到的,前端 eUML 要求您使用許多宏。頭文件 boost/msm/front/euml/common.hpp 包含所有 eUML 宏的定義,這使其成為一個有用的參考。

練習

為可以關閉、打開或傾斜的窗口創建狀態機。關閉的窗戶可以打開或傾斜。如果不先關閉打開的窗戶,則無法傾斜它。傾斜的窗戶也不能在不先關閉的情況下打開。通過打開和傾斜您的窗口幾次來測試您的狀態機。使用 current_state() 將狀態寫入標準輸出。

擴展狀態機:窗戶應該是智能家居的一部分。狀態機現在應該計算窗戶打開和傾斜的頻率。要測試您的狀態機,請打開并傾斜您的窗口幾次。在程序結束時,將窗口打開的頻率和傾斜的頻率寫入標準輸出。

原文鏈接:https://yamagota.blog.csdn.net/article/details/128204831

欄目分類
最近更新