我正在尝试使用boost::msm实现一个简单的协议。当数据包到达时,它们将被处理并调度到状态机 (SM( 进行相应的处理。
我的 pkt 类(即 pkt1(需要一个 fsm 的句柄,允许它调用fsm->process_event(...)
(当然,我会在 pkt1.h 的顶部添加#include "myfsm.h"
(。
目前为止,一切都好。但是,如果我的状态机(比如 State1(需要通过发送数据包本身来响应该数据包怎么办?现在我将"pkt1.h"标头包含在"state1.h"的顶部,以便我可以创建 Pkt1 的实例并调用其 send(( 函数。
正如您可能猜到的那样,最终包含会导致"循环依赖">
可以找到示例代码(有错误(:https://wandbox.org/permlink/IlFsUQyLPLrLl2RW(这是我第一次使用wandbox,希望一切正常(
注(在"state1.h"文件中,删除#include "pkt1.h"
和on_entry(..)... Pkt1 pkt; pkt.send();
以使其可编译。
问题:
1( 我应该如何解决这种循环依赖关系?
2(我认为前进的方法是为我的Pkt1类添加一个实现文件(.cpp并将#include "myfsm.h"
转移到该文件,从而打破循环依赖。但是如何转发声明头文件中的MyFsm
呢?
3(我是boost::msm/CRTP的新手,代码让我感到困惑。当我没有将相应的标头包含在 state1.h 时,State1 如何访问MyFsm
?(可能是因为MyFsm
派生自包含其标头的函子前端/后端,并允许虚拟函数调用相应的 MyFsm 函数!??(
非常感谢您的时间和帮助。
代码包括:
-
事件.h
#ifndef EVENTS #define EVENTS // ----- Events struct Event1 {}; struct Event2 {}; #endif // EVENTS
-
主.cpp
#include <iostream> #include "events.h" #include "myfsm.h" #include "pkt1.h" int main() { MyFsm fsm; fsm.start(); //fsm.process_event(Event1()); Pkt1 rcvdPkt; rcvdPkt.dispatch(&fsm); return 0; }
-
myfsm.h
//MyFsm.h #ifndef MYFSM #define MYFSM #include <iostream> #include <boost/msm/back/state_machine.hpp> #include <boost/msm/front/state_machine_def.hpp> #include <boost/msm/front/functor_row.hpp> #include "state1.h" #include "state2.h" #include "events.h" namespace msm = boost::msm; namespace msmf = boost::msm::front; namespace mpl = boost::mpl; struct MyFsm_ : msmf::state_machine_def<MyFsm_> { struct State1_ : State1 {}; // use public inheritance struct State2_ : State2 {}; // use public inheritance // Set initial state typedef State1_ initial_state; // Transition table struct transition_table:mpl::vector< msmf::Row < State1_, Event1, State2_, msmf::none, msmf::none > >{}; }; // Pick a back-end typedef msm::back::state_machine<MyFsm_> MyFsm; #endif // MYFSM
-
PKT1.H
#ifndef PKT1 #define PKT1 #include "myfsm.h" #include "events.h" class Pkt1 { public: Pkt1() {} void dispatch(MyFsm *fsm){ fsm->process_event(Event1()); } void send(){std::cout<<"pkt1 sent out ..."<<std::endl;} }; #endif // PKT1
-
状态1.h
//State1.h #ifndef STATE1 #define STATE1 #include <iostream> #include <boost/msm/back/state_machine.hpp> #include <boost/msm/front/state_machine_def.hpp> #include <boost/msm/front/functor_row.hpp> #include "pkt1.h" //comment this line to resolve the compliation error namespace msm = boost::msm; namespace msmf = boost::msm::front; namespace mpl = boost::mpl; struct State1:msmf::state<> { // Entry action template <class Event,class Fsm> void on_entry(Event const&, Fsm& ) const { std::cout << "State1::on_entry()" << std::endl; Pkt1 pkt; pkt.send();//comment this line to resolve the compliation error } // Exit action template <class Event,class Fsm> void on_exit(Event const&, Fsm&) const { std::cout << "State1::on_exit()" << std::endl; } }; #endif // STATE1
-
状态2.h
//State2.h #ifndef STATE2 #define STATE2 #include <iostream> #include <boost/msm/back/state_machine.hpp> #include <boost/msm/front/state_machine_def.hpp> #include <boost/msm/front/functor_row.hpp> namespace msm = boost::msm; namespace msmf = boost::msm::front; namespace mpl = boost::mpl; struct State2:msmf::state<> { // Entry action template <class Event,class Fsm> void on_entry(Event const&, Fsm&) const { std::cout << "State2::on_entry()" << std::endl; } // Exit action template <class Event,class Fsm> void on_exit(Event const&, Fsm&) const { std::cout << "State2::on_exit()" << std::endl; } }; #endif // STATE2
1(我应该如何解决这种循环依赖关系?
2(我认为前进的方法是为我的Pkt1类添加一个实现文件(.cpp(,并将 #include"myfsm.h"转移到该文件中,从而打破循环依赖。但是如何在头文件中转发声明 MyFsm?
正确。在Pkt1.h
中,你会转发声明MyFsm
,但它只是一些模板化提升类型的typedef。这里最简单的方法是复制 typedef(或使用(,同时向前声明您用作模板参数的类:
#include <boost/msm/back/state_machine.hpp>
struct MyFsm_;
using MyFsm = boost::msm::back::state_machine<MyFsm_>;
(如果多次使用此部分,则可能应将其放入标头中以避免代码重复(。
然后将所有函数实现移动到Pkt1.cpp
中,同时将声明保留在标头中。这是有效的,因为(或只要(你在那里的所有函数只接受对MyFsm
的指针或引用,因为编译器在这一点上不需要知道"它是一个指针"之外的更多。
Pkt1.h
:
#include <boost/msm/back/state_machine.hpp>
struct MyFsm_;
using MyFsm = boost::msm::back::state_machine<MyFsm_>;
class Pkt1
{
public:
Pkt1() {}
void dispatch(MyFsm *fsm);
void send();
};
Pkt1.cpp
:
#include "pkt1.h"
#include "myfsm.h"
#include "events.h"
#include <iostream>
void Pkt1::dispatch(MyFsm *fsm)
{
fsm->process_event(Event1());
}
void Pkt1::send()
{
std::cout<<"pkt1 sent out ..."<<std::endl;
}
演示:https://wandbox.org/permlink/5zMsbolOMPN0biaY
3(我是boost::msm/CRTP的新手,代码让我感到困惑。当我没有将相应的标头包含在状态 1.h 时,状态 1 如何访问 MyFsm?(可能是因为 MyFsm 派生自包含其标头的函子前端/后端,并允许虚拟函数调用相应的 MyFsm 函数!??(
这里的关键是on_entry
和on_exit
是模板函数。它们的代码仅在使用它们时生成 - 例如在 FSM 实现中(在 boost 内部,我们在这里看不到它(。这就是为什么它们必须位于标头中:当编译器实例化(即为函数模板的实例生成代码(时,完整的函数体必须对编译器可见。此时,模板参数Fsm
被替换为MyFsm
(以及Event
的事件之一(,因此一切都是已知的并解决的。
我建议阅读翻译单元以及C/C++编译器如何生成代码(即.h
和.cpp
文件会发生什么(。一旦你明白了这一点,很多事情都应该到位。