带策略的boost::sml状态机能否复制?如何嵌入C++类?
Boost.SML带队列策略状态机的复制与类嵌入方案
一、无法复制的核心原因
当给Boost.SML状态机配置defer_queue或process_queue策略时,内部会持有不可复制的队列对象(比如带锁的容器、内部状态缓存),C++会自动禁用默认的拷贝构造与赋值运算符,这就是复制失败的根本原因。
二、复制技巧:仅限特定场景
如果必须实现复制,需要手动编写拷贝逻辑,但存在局限性:
- 首先要复制状态机的当前状态;
- 重新投递源状态机队列中的所有事件到新状态机;
- 需自定义可复制的队列适配器(因为SML内部队列通常是私有成员,无法直接访问)。
示例代码框架:
#include <boost/sml.hpp> #include <queue> namespace sml = boost::sml; // 自定义可复制的队列适配器,暴露内部队列访问接口 template<typename T> struct CopyableQueue { std::queue<T> q; void push(const T& e) { q.push(e); } bool empty() const { return q.empty(); } T pop() { auto e = q.front(); q.pop(); return e; } // 支持拷贝构造 CopyableQueue(const CopyableQueue& other) : q(other.q) {} }; // 状态机定义 struct MySm { auto operator()() const { using namespace sml; return make_transition_table( *"idle"_s + event<EvStart> = "running"_s, "running"_s + event<EvStop> = "idle"_s ); } }; // 带自定义可复制队列的状态机类型 using SmWithCopyableQueue = sml::sm<MySm, sml::process_queue<CopyableQueue>>; // 自定义拷贝函数 SmWithCopyableQueue copy_sm(const SmWithCopyableQueue& src) { SmWithCopyableQueue dst; // 复制当前状态 dst.set_current_state(src.current_state()); // 复制队列事件(通过自定义队列的公开接口) auto& src_queue = src.template get<CopyableQueue>(); auto& dst_queue = dst.template get<CopyableQueue>(); dst_queue.q = src_queue.q; return dst; }
三、嵌入C++类的可行方案
如果不想折腾复制逻辑,嵌入类有以下几种选择:
1. 堆指针(最省心的通用方案)
用std::unique_ptr或std::shared_ptr封装状态机,避免直接处理复制问题,灵活控制生命周期:
class MyClass { private: std::unique_ptr<SmWithQueue> sm_; public: MyClass() : sm_(std::make_unique<SmWithQueue>()) {} // 若类需要复制,改用shared_ptr并实现拷贝构造 MyClass(const MyClass& other) : sm_(std::make_shared<SmWithQueue>(*other.sm_)) {} };
2. 直接嵌入(类无需复制时使用)
如果你的类本身不需要拷贝能力(比如标记为不可复制,或仅支持移动),可以直接把状态机作为成员变量:
#include <boost/noncopyable.hpp> class MyClass : boost::noncopyable { private: SmWithQueue sm_; public: MyClass() = default; // 状态机通常支持移动,可默认生成移动构造/赋值 MyClass(MyClass&&) = default; MyClass& operator=(MyClass&&) = default; };
3. std::optional延迟初始化
如果状态机需要按需初始化(比如依赖构造后的数据),用std::optional包裹:
class MyClass { private: std::optional<SmWithQueue> sm_; public: void init_sm() { sm_.emplace(); // 按需初始化状态机 } // 若类需要复制,结合自定义复制逻辑处理optional MyClass(const MyClass& other) { if (other.sm_) { sm_.emplace(copy_sm(*other.sm_)); } } };
四、总结
- 带队列策略的SML状态机默认不可复制,自定义复制逻辑复杂度高,仅在必要场景使用;
- 嵌入类时优先选择堆指针或直接嵌入(类不可复制),这两种方案实现简单、稳定性高。
内容的提问来源于stack exchange,提问作者DrDreadful
相关产品推荐
相关产品推荐

