std::variant适合状态少、数据差异大、切换不频繁的场景;高频切换或需统一接口时,虚函数更优。应只存状态标识和必要数据,避免对象拷贝与资源管理问题,用std::holds_alternative快速判断,transition方法集中处理on_enter/on_exit。

用 std::variant 实现状态机,不是不行,但容易掉进类型擦除和访问安全的坑里——它适合状态数少、状态数据差异大、且不频繁切换的场景;高频切换或需统一接口调度时,std::variant 反而比虚函数更重。
为什么不用 std::variant 直接存状态对象?
很多人一上来就写 std::variant<idlestate runningstate errorstate></idlestate>,以为“类型安全+零虚表开销”就赢了。但问题立刻浮现:
-
std::variant的构造/赋值会触发完整对象拷贝(除非移动语义显式支持),而状态对象若含资源(如std::thread、std::mutex)则极易出错 - 每次状态转换都要用
std::visit或std::get_if显式分发,逻辑分散,易漏分支,编译期无法约束所有状态都被处理 - 无法在运行时统一调用
on_enter()/on_exit()——因为各状态类接口不强制一致,std::variant本身不提供多态调度能力
怎么让 std::variant 真正轻量又可控?
核心思路:不把状态“对象”塞进去,而是只存状态的“标识 + 必要数据”,把行为逻辑外提。例如:
struct IdleData { bool auto_start{false}; };
struct RunningData { int progress{0}; };
struct ErrorData { int code{0}; };
using State = std::variant<:monostate idledata runningdata errordata>;
</:monostate>
这样避免了对象生命周期管理问题,也规避了拷贝资源的风险。关键操作建议:
- 用
std::holds_alternative<runningdata>(state)</runningdata>做快速类型判断,比std::visit开销小 - 状态迁移统一走一个
transition(State&& next)方法,在其中集中处理on_exit/on_enter逻辑(比如根据旧值/新值做日志或清理) - 所有数据成员保持 trivially copyable;若必须存非 trivial 类型(如
std::string),改用std::unique_ptr包裹,避免std::variant构造时异常传播
std::visit 分发时最常踩的三个坑
一旦要用 std::visit,下面这些错误几乎必现:
- 忘记处理
std::monostate:初始化时用std::monostate{}表示“无状态”,但std::visit不会自动 fallback,漏写分支直接 crash - lambda 捕获方式错误:写成
[&](auto&& s) { ... }可能导致对临时RunningData的悬垂引用;应改为值捕获或显式decltype(s)转换 - 返回类型不一致:不同 lambda 分支返回不同类型,
std::visit推导失败;统一用std::optional<t></t>或 void + 提前 return 控制流
推荐写法:
std::visit([](const auto& s) -> void {
using T = std::decay_t<decltype>;
if constexpr (std::is_same_v<t idledata>) {
log("entering idle");
} else if constexpr (std::is_same_v<t runningdata>) {
log("progress: ", s.progress);
}
}, state);
</t></t></decltype>
什么时候该果断放弃 std::variant?
以下任一条件成立,就别硬撑了:
- 状态超过 5 种,且每种都有独立行为逻辑(这时
std::visit分支爆炸,维护成本陡增) - 需要从外部(比如另一个线程)安全地触发状态变更并等待完成(
std::variant无内置同步机制,得自己加std::mutex+ 条件变量,反而不如带虚函数的基类清晰) - 状态之间存在继承关系(如
NetworkError是ErrorState的子类)——std::variant不支持子类型多态
这时候,一个带 virtual ~State() = default 和纯虚 handle(Event) 的基类,配合 std::unique_ptr<state></state>,反而更轻、更可测、更容易单元测试模拟。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











