如何减少Boost::Ext SML有限状态机转换表的重复转换?
如何在Boost::Ext SML中复用状态转换逻辑
问题场景
你正在用Boost::Ext SML构建包含A、B、C、D状态的有限状态机,大量状态共享通用转换逻辑(比如A/B/C都能通过GoToD事件转到D),当前手写的转换表冗余且易出错,尝试过模板辅助函数但不知道如何合并到主表,且不想用预处理器宏。
当前冗余的转换表示例:
struct MyStateMachine { auto operator()() const noexcept { return sml::make_transition_table( *sml::state<A> + sml::event<GoToB> = sml::state<B>, sml::state<A> + sml::event<GoToC> = sml::state<C>, sml::state<A> + sml::event<GoToD> = sml::state<D>, sml::state<B> + sml::event<GoToA> = sml::state<A>, sml::state<B> + sml::event<GoToC> = sml::state<C>, sml::state<B> + sml::event<GoToD> = sml::state<D>, sml::state<C> + sml::event<GoToA> = sml::state<A>, sml::state<C> + sml::event<GoToB> = sml::state<B>, sml::state<C> + sml::event<GoToD> = sml::state<D>, sml::state<D> + sml::event<GoToA> = sml::state<A>, sml::state<D> + sml::event<GoToB> = sml::state<B>, sml::state<D> + sml::event<GoToC> = sml::state<C>, ); } };
解决方案
1. 直接使用SML的多状态转换语法(最优解)
SML支持在state<>中传入多个状态类型,一次性给所有这些状态定义相同的转换逻辑,这是最简洁的方式:
struct MyStateMachine { auto operator()() const noexcept { return sml::make_transition_table( // 标记初始状态 *sml::state<A>, // 多状态共享转换:A/B/C触发GoToD都转到D sml::state<A, B, C> + sml::event<GoToD> = sml::state<D>, // B/C/D触发GoToA都转到A sml::state<B, C, D> + sml::event<GoToA> = sml::state<A>, // A/C/D触发GoToB都转到B sml::state<A, C, D> + sml::event<GoToB> = sml::state<B>, // A/B/D触发GoToC都转到C sml::state<A, B, D> + sml::event<GoToC> = sml::state<C> ); } };
如果需要给通用转换添加守卫条件或动作,直接追加即可:
// 带守卫和动作的多状态转换 sml::state<A, B, C> + sml::event<GoToD> [can_go_to_d] / log_transition = sml::state<D>,
2. 利用超状态(Super State)复用转换
如果你的状态有明确的逻辑分组(比如A/B/C属于同一类状态),可以定义一个基类作为超状态,让子状态继承它,然后给超状态定义转换,所有子状态会自动继承这些逻辑:
// 定义超状态 struct CommonStates {}; // 子状态继承超状态 struct A : CommonStates {}; struct B : CommonStates {}; struct C : CommonStates {}; struct D {}; // 状态机定义 struct MyStateMachine { auto operator()() const noexcept { return sml::make_transition_table( *sml::state<A>, // 所有CommonStates的子状态都能响应GoToD转到D sml::state<CommonStates> + sml::event<GoToD> = sml::state<D>, // 单独定义D的转换 sml::state<D> + sml::event<GoToA> = sml::state<A>, sml::state<D> + sml::event<GoToB> = sml::state<B>, sml::state<D> + sml::event<GoToC> = sml::state<C> ); } };
3. 模板化生成转换条目(复杂场景复用)
如果需要更灵活的复用逻辑(比如不同状态组有不同的通用转换),可以用模板生成转换条目,再通过tuple展开合并到主表:
// 生成单个状态的通用转换 template <typename From> auto single_state_transitions() { return std::make_tuple( sml::state<From> + sml::event<GoToA> = sml::state<A>, sml::state<From> + sml::event<GoToB> = sml::state<B>, sml::state<From> + sml::event<GoToC> = sml::state<C>, sml::state<From> + sml::event<GoToD> = sml::state<D> ); } // 批量生成多个状态的通用转换 template <typename... FromStates> auto multi_state_transitions() { return std::tuple_cat(single_state_transitions<FromStates>()...); } // 辅助函数:将tuple转换为make_transition_table可接受的参数包 template <typename Tuple, std::size_t... Is> auto expand_tuple(Tuple&& t, std::index_sequence<Is...>) { return sml::make_transition_table(std::get<Is>(std::forward<Tuple>(t))...); } template <typename Tuple> auto expand_tuple(Tuple&& t) { return expand_tuple( std::forward<Tuple>(t), std::make_index_sequence<std::tuple_size_v<std::decay_t<Tuple>>>{} ); } // 主状态机定义 struct MyStateMachine { auto operator()() const noexcept { // 生成A/B/C/D的通用转换 auto common_trans = expand_tuple(multi_state_transitions<A, B, C, D>()); return sml::make_transition_table( // 特殊转换:覆盖A->B的初始状态逻辑 *sml::state<A> + sml::event<GoToB> = sml::state<B>, // 合并通用转换 common_trans ); } };
注意事项
- 如果存在重复的转换定义(比如特殊转换和通用转换冲突),SML会以后定义的转换为准,所以建议将特殊转换放在通用转换之前,避免意外覆盖。
- 带守卫条件的转换优先级高于无守卫的,即使定义顺序靠前。
内容的提问来源于stack exchange,提问作者jlev
相关产品推荐
相关产品推荐

