状态类必须用指针或引用传递,否则值传递引发对象切片致虚函数退化为基类实现;State 必须是纯虚类,Context 持有 std::unique_ptr<State>,状态转移逻辑统一收口于 Context::handleEvent(),禁止在状态类内 new/delete,推荐工厂函数创建状态并实现 name() 便于调试。

状态类必须用指针或引用传递,否则对象切片
直接传值会导致派生状态对象被截断成基类,所有虚函数调用都退化为基类实现。这是 C++ 状态模式最隐蔽的崩溃源头。
- 状态接口
State必须是纯虚类,禁止定义非虚成员变量 - 上下文类(如
Context)中保存的是State*或std::unique_ptr<state></state>,绝不能是State值类型 - 切换状态时用
std::make_unique<concretestatea>()</concretestatea>,别写state = ConcreteStateA()
示例错误:state = IdleState(); → 切片;正确:state = std::make_unique<idlestate>();</idlestate>
状态转移逻辑不能写在状态类内部
把「什么条件下该切到哪个状态」的判断逻辑塞进 handleEvent() 里,会快速导致状态类膨胀、耦合失控。状态类只负责响应,不负责决策。
- 转移规则统一收口到
Context::handleEvent()中 - 每个状态类的
handleEvent()只做两件事:执行当前行为、返回建议的新状态 ID(比如枚举StateId::RUNNING)或者 void - 如果要用 ID 驱动转移,
Context内部维护一个std::map<stateid std::unique_ptr>></stateid>映射表
常见错误现象:IdleState::handleEvent() 里 new 一个 RunningState 并赋给 context->state —— 这会让 Context 失去对状态生命周期的控制权。
立即学习“C++免费学习笔记(深入)”;
避免裸 new / delete,优先用智能指针 + 工厂函数
手动管理状态对象内存,在异常路径或频繁切换场景下极易泄漏或 double-delete。
- 用
std::unique_ptr<state></state>替代State*,构造时用std::make_unique() - 所有状态创建封装进工厂函数,例如
createState(StateId id),便于后续加日志、计数或替换策略 - 如果状态需共享数据,通过
Context&引用传入,不要在状态类里存Context*(容易悬空)
性能影响:std::unique_ptr 零开销抽象,但每次 reset() 会有一次小内存分配;若状态极少切换,可预分配几个对象用对象池优化。
调试时打印状态名比打印地址有用得多
运行时看到 0x7ffee21a8b30 完全无法定位问题,而 "IdleState" 能立刻对应到代码分支。
- 在基类
State加一个纯虚函数virtual const char* name() const = 0; - 每个派生类实现它,返回字符串字面量(无内存分配)
-
Context::handleEvent()开头加日志:std::cout name()
容易被忽略的点:没重写 name() 就编译不过,强制每个状态自我标识;调试信息不依赖 RTTI,兼容 -fno-rtti 环境。











