【问题标题】:How to use forward declaration with boost::msm to avoid circular dependency?如何使用带有 boost::msm 的前向声明来避免循环依赖?
【发布时间】:2018-06-04 10:55:31
【问题描述】:

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

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

到目前为止一切顺利。但是,如果我的状态机(比如 State1)需要通过发送数据包本身来对该数据包做出反应怎么办?现在我将在“state1.h”的顶部包含“pkt1.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 如何访问MyFsm 而我没有在 state1.h 中包含相应的标头? (可能是因为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
    

【问题讨论】:

    标签: c++ boost template-meta-programming code-separation boost-msm


    【解决方案1】:

    1) 我应该如何解决这种循环依赖?

    2) 我认为前进的方向是为我的 Pkt1 类添加一个实现文件 (.cpp) 并将 #include "myfsm.h" 传输到该文件,从而打破循环依赖。但是如何在头文件中转发声明 MyFsm?

    正确。在Pkt1.h 中,您将转发声明MyFsm,但它只是某些模板化 boost 类型的 typedef。这里最简单的方法是复制 typedef(或 using),同时将您正在使用的类作为模板参数进行前向声明:

    #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 的新手,代码让我很困惑。当我没有在 state1.h 中包含相应的标头时,State1 如何访问 MyFsm? (可能是因为 MyFsm 派生自 functor 前端/后端,其中包含其标头并允许虚函数调用相应的 MyFsm 函数!!??)

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

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

    【讨论】:

    • 我已经尝试过您的建议,但无法编译。您可以找到代码:(wandbox.org/permlink/TEhccGVJd6ApuKMn)感谢您在第 3 点的提示。
    • 哦,我的错,我错过了 MyFsm 只是一个 typedef。 See here - 我还编辑了我的答案。请注意,您还必须将MyPkt1.cpp 添加到编译器选项中,否则将无法编译!
    • 请更正您的帖子,以便我将其标记为已解决。非常感谢你的帮助。最后一个问题:为什么要将 .cpp 文件添加到编译器选项中,这样做/实现什么?
    • 以便编译器为每个.cpp(即每个翻译单元)创建一个目标文件。然后链接器将它们全部链接到一个可执行文件(或库)中。 Wandbox 会自动将您的 main.cpp 添加到选项中,其他任何您必须自己添加的选项。我不会在这里更深入地介绍,因为这正是我建议您阅读的主题,我无法在这里进行足够深入的介绍。
    猜你喜欢
    • 2012-04-24
    • 1970-01-01
    • 2013-02-06
    • 1970-01-01
    • 2012-10-08
    • 2018-09-03
    • 2012-02-15
    • 2013-04-02
    • 1970-01-01
    相关资源
    最近更新 更多