基于模板的简易FSM构造优化及模板使用咨询
构造初始状态的简洁方案
针对你当前需要手动嵌套std::make_unique的问题,这里有几种更简洁的实现方式:
1. 带完美转发的模板构造函数
直接在FSM中实现模板构造函数,接受状态子类的构造参数,内部自动创建std::unique_ptr,无需用户手动处理:
#include <memory> #include <utility> #include <type_traits> class State { public: virtual ~State() = default; virtual void Enter() = 0; virtual void Exit() = 0; }; class FSM { public: // 模板构造函数:接受任意State子类的构造参数 template <typename DerivedState, typename... Args> explicit FSM(Args&&... args) : current_state_(std::make_unique<DerivedState>(std::forward<Args>(args)...)) { // 编译期检查:确保传入类型是State的子类 static_assert(std::is_base_of_v<State, DerivedState>, "DerivedState must inherit from State"); } // 状态切换示例 void TransitionTo(std::unique_ptr<State> new_state) { if (current_state_) current_state_->Exit(); current_state_ = std::move(new_state); current_state_->Enter(); } private: std::unique_ptr<State> current_state_; };
调用时直接传入初始状态的构造参数即可:
class InitialState : public State { public: InitialState(int param, const std::string& msg) { /* 构造逻辑 */ } void Enter() override { /* 进入动作 */ } void Exit() override { /* 退出动作 */ } }; // 简洁调用 FSM fsm{42, "initial state"};
若担心构造函数重载冲突,可改用std::in_place_type_t显式指定状态类型:
template <typename DerivedState, typename... Args> explicit FSM(std::in_place_type_t<DerivedState>, Args&&... args) : current_state_(std::make_unique<DerivedState>(std::forward<Args>(args)...)) { static_assert(std::is_base_of_v<State, DerivedState>, "DerivedState must inherit from State"); } // 调用方式 FSM fsm{std::in_place_type<InitialState>, 42, "initial state"};
2. 静态工厂方法
把构造逻辑封装成静态成员函数,代码可读性更高:
class FSM { public: template <typename DerivedState, typename... Args> static FSM Create(Args&&... args) { static_assert(std::is_base_of_v<State, DerivedState>, "DerivedState must inherit from State"); return FSM{std::make_unique<DerivedState>(std::forward<Args>(args)...)}; } // 其他方法... private: // 私有构造函数:仅允许工厂方法调用 explicit FSM(std::unique_ptr<State> initial_state) : current_state_(std::move(initial_state)) {} std::unique_ptr<State> current_state_; };
调用方式:
auto fsm = FSM::Create<InitialState>(42, "initial state");
模板在FSM场景的评价与建议
优点
- 编译期类型安全:通过模板和
static_assert/C++20概念,编译阶段就能确保状态类型符合要求,避免运行时类型错误。 - 零额外运行开销:模板实例化在编译期完成,不会引入虚函数之外的额外开销;纯模板实现的无多态FSM,甚至可以让编译器完全优化掉状态切换的冗余代码。
- 接口灵活性高:可针对特定状态类型做模板特化,或通过模板参数定制FSM行为(比如是否记录状态切换日志)。
缺点
- 编译时间增加:每个不同的状态组合都会生成独立的模板实例,复杂FSM可能导致编译耗时变长。
- 错误信息复杂:模板错误通常冗长且难以定位,多层模板嵌套时排查成本更高。
- 潜在代码膨胀:大量不同状态类型可能导致可执行文件体积增大,不过现代编译器的链接时优化(LTO)能有效缓解这个问题。
实用建议
- 用C++20概念约束状态类型:替代
static_assert,让错误信息更清晰,同时明确FSM对状态的接口要求:template <typename T> concept StateConcept = requires(T t) { { t.Enter() } -> std::same_as<void>; { t.Exit() } -> std::same_as<void>; }; // 在模板构造函数中添加约束 template <StateConcept DerivedState, typename... Args> explicit FSM(Args&&... args) { /* ... */ } - 分离通用逻辑与模板代码:把FSM核心逻辑(比如状态切换流程)放在非模板基类中,模板部分只负责状态实例化和类型检查,减少代码重复。
- 避免过度模板化:如果所有状态遵循统一接口、无需定制逻辑,用多态+
std::unique_ptr的基础方案足够,模板仅用来简化构造即可。 - 补充清晰文档:模板接口的使用方式不如普通接口直观,需在注释中明确构造参数、模板参数含义及合法状态类型要求。
内容的提问来源于stack exchange,提问作者mca2
相关产品推荐
相关产品推荐

