我正在尝试使用boost::msm实现一个简单的协议(protocol)。当数据包到达时,将对它们进行处理并将其分派(dispatch)到状态机(SM),以进行相应的处理。
我的pkt类(即Pkt1)需要fsm的句柄,以使其可以调用fsm->process_event(...)
(当然,我会在pkt1.h的顶部添加#include "myfsm.h"
)。
到现在为止还挺好。但是,如果我的状态机(例如State1)需要通过发送数据包本身来对该数据包使用react,该怎么办?现在,我将在“state1.h”的顶部包括“pkt1.h” header ,以便我可以创建Pkt1的实例并调用其send()函数。
您可能会猜到,最后的包含会导致“循环依赖”
可以找到示例代码(带有错误):https://wandbox.org/permlink/IlFsUQyLPLrLl2RW(我第一次使用wandbox,希望一切都OK)
注)在“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
从函子的前端/后端派生而来,它的 header 包含在其中,并允许虚函数调用相应的MyFsm函数!!!)
非常感谢您的宝贵时间和事先的帮助。
包含的代码:
#ifndef EVENTS
#define EVENTS
// ----- Events
struct Event1 {};
struct Event2 {};
#endif // EVENTS
#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
#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
#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
//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
//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
最佳答案
正确。在Pkt1.h
中,您可以转发声明MyFsm
,但这只是一些模板化boost类型的typedef。这里最简单的方法是在向前声明要用作模板参数的类时复制typedef(或使用):
#include <boost/msm/back/state_machine.hpp>
struct MyFsm_;
using MyFsm = boost::msm::back::state_machine<MyFsm_>;
(如果多次使用此部分,则可能应将其放入 header 中以避免代码重复)。
然后将所有函数实现都移到
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
这里的关键是
on_entry
和on_exit
是模板函数。仅在使用它们时才生成它们的代码-例如在FSM实现中(在boost内部,我们在这里看不到)。这就是为什么它们必须位于 header 中的原因:完整的函数体在实例化功能模板(即为功能模板的实例生成代码)时对于编译器必须是可见的。那时,模板参数Fsm
替换为MyFsm
(以及Event
的事件之一),因此一切都已知并且可以解决。我建议您阅读翻译单元以及C / C++编译器如何生成代码(即
.h
和.cpp
文件会发生什么)。一旦您了解了这一点,很多事情就应该解决。