【问题标题】:Sub state-machine as inital active state in boost::msm子状态机作为 boost::msm 中的初始活动状态
【发布时间】:2014-10-01 13:49:03
【问题描述】:

我希望子状态机成为状态机的“initial_state”。以下应该是代码的分解版本。

struct E {
};

struct A : public boost::msm::front::state<> {
    template <class TEvent, class TStateMachine>
    void on_entry(TEvent const& event, TStateMachine& stateMachine) {  
    }

    template <class TEvent, class TStateMachine>
    void on_exit(TEvent const& event, TStateMachine& stateMachine) {  
    }
};

struct B: public boost::msm::front::state<> {
    template <class TStateMachine>
    void on_entry(E const& event, TStateMachine& stateMachine) {  
    }

    template <class TStateMachine>
    void on_exit(E const& event, TStateMachine& stateMachine) {  
    }
};

struct SubMachineDefinition : public boost::msm::front::state_machine_def<SubMachineDefinition> {
    typedef boost::msm::front::state_machine_def<SubMachineDefinition> Base;

    typedef A initial_state;

    struct transition_table : boost::mpl::vector<
        typename Base::template _row<A, E, B>
    > {};

    // Added via suggestion of sehe
    template <class IE, class SM> void on_entry(IE const&, SM&) { std::cout << __PRETTY_FUNCTION__ << "\n"; }

    // Added via suggestion of sehe
    template <class IE, class SM> void on_exit (IE const&, SM&) { std::cout << __PRETTY_FUNCTION__ << "\n"; } 
};

struct SuperMachineDefinition : public boost::msm::front::state_machine_def<SuperMachineDefinition > {
    typedef SubMachineDefinition initial_state;

    struct transition_table : boost::mpl::vector<> {};
};

int main() {
    boost::msm::back::state_machine<SuperMachineDefinition> states;
    states.start();
    states.process_event(E()); // This crashes
}

虽然编译得很好,但当我调用 process_event 时,它会崩溃。这甚至可能吗?或者有办法调试吗?

编辑 1:

我现在在A::on_entry 上设置断点。当start 被调用时,它不会中断,所以我很明显的问题 - 为什么不使用子状态机!?

编辑 2:

有趣的是,当我将SubMachineDefinition 替换为boost::msm::back::state_machine&lt;SubMachineDefinition&gt;(如initial_state)时,我得到一个编译错误,指出它试图用InitEvent 而不是E 调用B::on_entry。我现在非常很困惑。

编辑 3:

实际上我确实为A 复制了错误的重载。我也确实应用了 sehe 的 SubMachineDefinition::on_entrySubMachineDefinition::on_exit 定义。尽管如此,我还是得到了编译错误,它试图用InitEvent 调用B::on_entry

【问题讨论】:

  • 你已经很困惑了。现在我不想毁了你的 2222 代表......也许如果你再得到 7 个青铜徽章。那是leet。

标签: visual-studio-2010 boost state-machine c++98 boost-msm


【解决方案1】:

正如您已经指出的,您需要将SubMachine 状态设为后端

typedef boost::msm::back::state_machine<SubMachineDefinition> SubMachine;
typedef SubMachine initial_state;

接下来,您需要准备好处理(或忽略)存在的InitEvent,以便SubMachine 知道是什么使其进入初始状态。

最简单且典型的方法是仅模板化事件参数:

struct A : public boost::msm::front::state<> {
    template <class IE, class SM> void on_entry(IE const&, SM&) { std::cout << __PRETTY_FUNCTION__ << "\n"; } 
    template <class IE, class SM> void on_exit (IE const&, SM&) { std::cout << __PRETTY_FUNCTION__ << "\n"; } 
};

当然,您可以使用重载 as over 来显式处理某些事件:

struct A : public boost::msm::front::state<> {
    template <class IE, class SM> void on_entry(IE const&, SM&) { std::cout << __PRETTY_FUNCTION__ << "\n"; } 
    template <class SM> void on_entry(E const&, SM&)            { std::cout << __PRETTY_FUNCTION__ << "\n"; } 
    template <class IE, class SM> void on_exit (IE const&, SM&) { std::cout << __PRETTY_FUNCTION__ << "\n"; } 
};

你可能想写成这样

template <class SM> void on_entry(typename SM::InitEvent const&, SM&) { std::cout << __PRETTY_FUNCTION__ << "\n"; } 

如果不是子状态机,那确实可行。这是因为 InitEvent 实际上是 msm::state_machine&lt;SuperMachineDefinition&gt;::InitEvent,而不是 msm::state_machine&lt;SubMachineDefinition&gt;::InitEvent。您可以执行大量模板魔术来显式检测 InitEvent,但您可能应该只使用所示的重载。


UPDATE 回应 cmets:

如果有人可以向我解释,实现如何搜索它调用 InitEvent 的状态,那就太好了

  • 关键是它/不/搜索特定状态来运行它;它使用访问者模式来做到这一点,这意味着静态地,所有子状态都必须准备好接受InitEvent

    运行时日志显示仅调用了A::on_entry(InitEvent const&amp;, SM&amp;)。 你可以简单地“吃掉”InitEvent。 (见上文,关于使用重载)。

    最后归结为关注点分离:从超级机器看,子机器是不透明的状态。 The same limitation is mentioned in the documentation (#limitation-submachine) 关于事件属性:

    但是,子机有一个限制。如果一个子机的子状态有一个入口动作需要一个特殊的事件属性(比如给定的方法),编译器会要求所有进入这个子机的事件都支持这个属性。

    当然,要处理 InitEvent,您需要执行 SFINAE 魔法,因为您可以重载您所期望的事件类型。

你能看看你的结构B,为什么不使用E?这就是原因(作弊),为什么你的例子有效

  • 这不是作弊。您的代码根本不满足子机状态on_entry 方法的要求。

    既然您已经识别出导致代码中问题的特定元素,那么将其作为解决方案的途径可能是明智的...


完整的工作示例Live On Coliru

#include <boost/msm/msm_grammar.hpp>
#include <boost/msm/front/state_machine_def.hpp>
#include <boost/msm/back/state_machine.hpp>
#include <iostream>

struct E {
};

struct A : public boost::msm::front::state<> {
    template <class IE, class SM> void on_entry(IE const&, SM&) { std::cout << __PRETTY_FUNCTION__ << "\n"; } 
    template <class SM> void on_entry(E const&, SM&)            { std::cout << __PRETTY_FUNCTION__ << "\n"; } 
    template <class IE, class SM> void on_exit (IE const&, SM&) { std::cout << __PRETTY_FUNCTION__ << "\n"; } 
};

struct B : public boost::msm::front::state<> {
    template <class IE, class SM> void on_entry(IE const&, SM&) { std::cout << __PRETTY_FUNCTION__ << "\n"; } 
    template <class IE, class SM> void on_exit (IE const&, SM&) { std::cout << __PRETTY_FUNCTION__ << "\n"; } 
};

struct SubMachineDefinition : public boost::msm::front::state_machine_def<SubMachineDefinition>
{
    typedef boost::msm::front::state_machine_def<SubMachineDefinition> Base;

    typedef A initial_state;

    struct transition_table : boost::mpl::vector<
        typename Base::template _row<A, E, B>,
        typename Base::template _row<B, E, A>
    > {};

    template <class IE, class SM> void on_entry(IE const&, SM&) { std::cout << __PRETTY_FUNCTION__ << "\n"; } 
    template <class IE, class SM> void on_exit (IE const&, SM&) { std::cout << __PRETTY_FUNCTION__ << "\n"; } 
};

struct SuperMachineDefinition : public boost::msm::front::state_machine_def<SuperMachineDefinition > {
    typedef boost::msm::back::state_machine<SubMachineDefinition> SubMachine;
    typedef SubMachine initial_state;

    struct transition_table : boost::mpl::vector<> {};
};

int main() {
    boost::msm::back::state_machine<SuperMachineDefinition> states;
    states.start();
    states.process_event(E());
    states.process_event(E());
    states.process_event(E());
    states.process_event(E());
    states.process_event(E());
}

【讨论】:

  • 我仍然有问题,使用了错误的B::on_entry 重载。如果有人可以向我解释实现如何搜索它调用 InitEvent 的状态,那就太好了。
  • 你能看看你的struct B,为什么不使用E!?这就是你的例子有效的原因(作弊)。
  • 我正在开会。如果您使用设计的框架,这不是作弊:) 我的桌面上有一条待处理的评论。耐心:)
  • @abergmeier 我已经在 UPDATE 下的答案中解决了您的投诉(评论太大了)。如果您对 MSM 中子机的工作方式真的不满意,也许您可​​以在 Boost 邮件列表中开始讨论。
  • 我忘了在我的回答中提到,如果你只是......不要依赖这些 generic 钩子,你可以防止“不直观的”on_entry 重载,而是使用过渡动作。 (文档 seem a bit out of date 关于您是否在操作方法中获得对 SM 的引用)