状态类必须是纯虚基类,只含纯虚函数和析构函数;状态切换由context统一控制,禁止state子类主动调用set_state;context用std::unique_ptr持有状态,禁用拷贝,handle()返回unique_ptr实现安全转移。

状态类必须是纯虚基类,且转移逻辑不能写在状态内部
状态模式的核心是把行为委托给当前状态对象,而不是在上下文(Context)里用 if/else 判断状态码。常见错误是让 State 子类自己调用 context->set_state(...),这会导致依赖倒置——状态不该知道上下文的具体类型,更不该主动触发切换。
正确做法是:状态子类只定义行为,所有状态变更由上下文统一控制;State 基类不持有 Context*,也不暴露 set_state 接口。
-
State应声明为纯虚类,只含纯虚函数(如handle())和析构函数 - 状态切换必须由
Context的成员函数触发,比如request()内部根据当前状态返回新状态指针 - 避免裸指针管理:用
std::unique_ptr<state></state>持有当前状态,确保自动释放
Context 类需支持运行时状态替换,且禁止拷贝
如果 Context 可被拷贝,而它持有的 std::unique_ptr<state></state> 会引发移动语义问题或浅拷贝崩溃。更关键的是,状态对象通常依赖 Context 的引用做回调,拷贝后引用失效。
典型错误是忘记禁用拷贝构造与赋值运算符,或误用 shared_ptr 试图“共享”状态——状态的生命周期应严格绑定于单个 Context 实例。
- 显式删除拷贝相关函数:
Context(const Context&) = delete;、Context& operator=(const Context&) = delete; - 状态切换接口建议返回
std::unique_ptr<state></state>,由Context::set_state()移动接管 - 若需从状态中访问
Context数据,通过构造时传入Context&引用(非指针),避免悬挂
避免在 handle() 中直接 new 状态对象导致内存泄漏
很多初学者在 State::handle() 里写 context->set_state(new ConcreteStateB);,既违反封装(状态不该创建下一个状态),又绕过智能指针管理,造成裸指针泄漏。
真正安全的做法是让每个状态的 handle() 返回一个 std::unique_ptr<state></state>,表示“我处理完后,上下文应该切换到这个新状态”。这样所有权清晰,且可统一做空检查。
std::unique_ptr<state> ConcreteStateA::handle(Context& ctx) {
std::cout (ctx);
}
</state>
- 所有状态构造函数接收
Context&,用于后续回调(如ctx.do_something()) -
Context::request()调用当前状态的handle(),并用移动语义更新自身状态 - 若某状态下不应切换,
handle()返回nullptr或保持原状态(返回std::move(m_state))
std::move 和 unique_ptr 的配合容易漏掉转移后的空状态检查
当 Context::set_state(std::unique_ptr<state>&& s)</state> 接收右值后,执行 m_state = std::move(s),此时 s 变为空。但如果后续代码仍尝试访问 s.get(),会解引用空指针。
更隐蔽的问题是:状态切换过程中,若新状态构造失败(比如抛异常),旧状态已被移动走,m_state 为空,而程序继续执行 request() 就会 crash。
- 在
set_state()内部,先完成新状态构造(无异常),再移动替换 - 每次调用
handle()前,Context::request()应断言m_state != nullptr - 调试时可在
State析构函数加日志,确认销毁时机是否符合预期
handle() 返回前人工校验。C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











