状态模式要求状态类无状态、不持有上下文指针、不直接修改Context数据,所有状态变更由Context统一调度,通过std::unique_ptr管理生命周期,避免循环依赖与内存失控。

状态模式在 C++ 里不是靠虚函数多态“假装”切换状态,而是必须让状态对象真正接管行为逻辑,并且避免状态类之间循环依赖或手动 new / delete 导致的生命周期失控。
状态接口设计必须用纯虚函数 + 值语义传递
接口不能带实现,也不能持有上下文指针(否则容易形成引用环)。典型错误是让 State 持有 Context* 并在内部调用 context->setState(...) —— 这会让状态变更逻辑散落在各处,违背“状态决定行为”的原则。
正确做法是:所有状态变更由 Context 统一调度,State 只负责响应事件并返回下一个状态(或 void,由 Context 决定是否切换):
struct State {
virtual ~State() = default;
virtual void handleEventA(Context& ctx) = 0;
virtual void handleEventB(Context& ctx) = 0;
// 不提供 setState 接口,也不持有 ctx 指针
};
- 用
Context&而非Context*,避免空指针风险,也明确生命周期归属 - 状态类本身应是无状态的(stateless),即不保存任何与上下文相关的字段;所有数据存在
Context中 - 若需复用状态实例(如单例状态),用
static const实例,而非每次new
状态类转换必须通过 std::unique_ptr 管理生命周期
手动 new StateX() + delete 是最常见崩溃源:忘记 delete、重复 delete、或在异常路径中泄漏。更隐蔽的问题是,两个状态类互相 #include 头文件导致编译失败 —— 比如 StateA.h 包含 StateB.h,而 StateB.h 又反向包含 StateA.h。
立即学习“C++免费学习笔记(深入)”;
解决方式是前置声明 + pimpl 风格延迟定义:
// Context.h
class Context {
std::unique_ptr<State> current_state_;
public:
void setState(std::unique_ptr<State> s) {
current_state_ = std::move(s);
}
void handleEventA() { current_state_->handleEventA(*this); }
};
- 所有具体状态类(
StateA,StateB)只在 .cpp 文件里实现和构造,头文件中仅声明类名 - 在
Context.cpp中#include "StateA.h"和"StateB.h",但头文件间不互相包含 - 切换状态时统一用
std::make_unique<StateB>(),不裸 new
避免在状态方法中直接修改 Context 成员变量
看似方便的写法:ctx.status_flag = true;,实际会快速污染 Context 的职责边界。一旦多个状态都往里塞字段,Context 就退化成一堆杂乱标志位的容器,失去“流程上下文”的语义。
应该把相关数据聚合成内聚结构,例如:
struct PaymentContext {
enum class Stage { Idle, AuthPending, Captured, Refunded };
Stage stage = Stage::Idle;
std::string tx_id;
double amount = 0.0;
// 所有状态变更走统一入口
void transitTo(Stage s) { stage = s; }
};
- 每个状态类只调用
ctx.transitTo(...)和读取必要字段,不直接赋值 - 如果某状态需要临时缓存数据(比如验证码 token),应作为该状态类的成员,而不是塞进
Context - 对
Context的修改应集中在少数几个语义清晰的接口上,便于审计和测试
状态模式最难的部分从来不是写几个虚函数,而是守住“状态即行为”的边界——一旦允许状态类越过接口直接操作上下文数据,整个模式就塌缩成一堆 if-else 的马甲。真正的转换成本不在代码量,而在团队能否一致认同:状态变更必须显式、可追踪、不可绕过。


















