如何使用boost :: msm进行前向声明以避免循环依赖?

我试图用boost :: msm实现一个简单的协议 . 当数据包到达时,它们被处理并分派到状态机(SM)以进行相应的处理 .

我的pkt类(即Pkt1)需要fsm的句柄,允许它调用 fsm->process_event(...) (当然我会将 #include "myfsm.h" 添加到pkt1.h的顶部) .

到现在为止还挺好 . 但是,如果我的状态机(比如说State1)需要通过自己发送数据包来对该数据包做出反应呢?现在我将“pkt1.h” Headers 包含在“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函数!! ??)

非常感谢您的时间和提前帮助 .

代码包括:

  • events.h
#ifndef EVENTS
#define EVENTS


// ----- Events
struct Event1 {};
struct Event2 {};

#endif // EVENTS
  • main.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
  • state1.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
  • state2.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 years ago

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_>;

(如果多次使用此部分,则应该将其放入 Headers 中以避免代码重复) .

然后将所有函数实现移动到 Pkt1.cpp ,同时保持标头中的声明 . 这是因为(或者只要)所有函数都只有指针或对 MyFsm 的引用,因为编译器在那时不需要知道多于"it's a pointer" .

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的新手,代码让我感到困惑 . 如果没有将相应的头部包含到state1.h中,State1如何访问MyFsm? (也许是因为MyFsm派生于其包含头部的仿函数前/后端,并允许虚函数调用相应的MyFsm函数!! ??)

这里的关键是 on_entryon_exit 是模板函数 . 它们的代码仅在使用时生成 - 例如在FSM实现中(在boost内部,我们在这里看不到它) . 这就是为什么它们必须在 Headers 中:当编译器实例化(即生成函数模板的实例的代码)时,编译器必须可以看到完整的函数体 . 此时,模板参数 Fsm 将替换为 MyFsm (以及 Event 的一个事件),因此一切都已知并且可以解决 .

我建议阅读翻译单元以及C / C编译器如何生成代码(即 .h.cpp 文件会发生什么) . 一旦你理解了这一点,很多事情都应该落到实处 .