状态模式的核心是委托而非继承,主体类通过指针委托行为给状态对象,用std::unique_ptr管理生命周期,避免裸指针问题;C++17可用std::variant替代虚函数实现零开销状态切换。

状态模式的核心不是继承,而是委托
状态模式的本质是把行为的分支逻辑从主体类中抽出来,让每个状态成为独立的对象。很多人一上来就想着用 if-else 或 switch 在主体类里判断状态,结果越写越胖、越改越脆——这不是状态模式,只是状态枚举加条件跳转。
真正有效的做法是:主体类(比如 Context)持有一个指向抽象状态接口的指针,所有行为都委托给当前状态对象执行。状态切换时,只换这个指针所指的对象,不改主体逻辑。
- 主体类不直接实现任何具体行为,只定义状态变更入口(如
request()、handle()) - 每个具体状态类(如
ConcreteStateA)实现同一接口,但行为完全不同 - 状态之间可以互相触发切换,比如
stateB->handle()内部调用context->setState(new StateC)
避免裸指针导致的内存泄漏和悬挂
用裸指针管理状态对象,最容易在频繁切换或异常路径下出问题:新状态 new 出来,旧状态忘了 delete;或者旧状态被 delete 后,指针没置空,下次访问就崩。
推荐用 std::unique_ptr 管理状态生命周期,既自动释放,又明确所有权:
立即学习“C++免费学习笔记(深入)”;
class Context {
private:
std::unique_ptr<State> state_;
public:
void setState(std::unique_ptr<State> s) {
state_ = std::move(s); // 自动释放旧对象
}
};
- 不要在状态对象内部保存
Context*并手动管理——容易循环引用或野指针 - 如果必须回调 context,用弱引用语义(如传入
Context&或Context*但不持有) - 避免在析构函数里调用虚函数(比如
~State()中调用context->setState(...)),此时虚表已部分销毁
状态切换时机错位:别在构造/析构里改状态
常见错误是在某个状态类的构造函数里立刻调用 context->setState(),或者在 handle() 执行中途强行切换,导致行为不可预测。
状态切换应发生在行为执行完成之后,且由明确的业务语义驱动:
- 比如“登录成功”后才从
LoginState切到LoggedInState,而不是一收到用户名就切 - 切换动作本身不应出现在状态对象的构造函数中——构造只负责初始化,不负责调度
- 如果一次请求可能引发多次状态变化(如连续提交失败→重试→超限→锁定),确保每次
setState都是原子的,中间不夹杂其他状态依赖操作
C++17 以后,考虑用 std::variant 替代虚函数方案
当状态数量少、行为简单、且不需要运行时动态增删状态类型时,std::variant + std::visit 是更轻量、零开销的替代方案。
它避免了虚函数调用、堆分配和指针间接访问,适合嵌入式或高频调用场景:
using State = std::variant<LockedState, ActiveState, IdleState>;
struct Context {
State state_;
void handle() {
std::visit([](auto& s) { s.handle(); }, state_);
}
};
- 不能动态新增状态类型(编译期确定)
- 状态间共享数据需通过
Context成员传递,不能像虚函数那样隐式绑定this - 调试时类型信息更清晰,gdb 能直接看到当前是哪个变体;但错误信息不如虚函数调用栈直观



















