用std::variant写状态机是为了安全管理异构状态数据,避免手动内存管理错误;应为每种状态定义独立struct,用std::visit强制全覆盖分支,状态变更须通过赋值构造新对象,非必要不滥用variant。

用 std::variant 写状态机,不是为了炫技,而是当状态携带的数据结构不同时,它能让你避免手动 reinterpret_cast、反复判空、漏析构这些硬伤。
为什么不用 enum + struct 手动管理状态数据?
常见错误现象:struct State { enum Type t; union { int fd; std::string token; }; }; —— 你得自己记哪个字段有效、自己调 token.~string()、自己防止 fd 被当成 string 读;一旦新增状态,所有访问点都要补判断逻辑,漏一处就是 std::bad_variant_access 或内存泄漏。
实操建议:
- 每个状态对应一个独立
struct,只放该状态真正需要的字段(比如Connecting { int sock_fd; };、Authenticating { std::string token; };) - 用
using State = std::variant<idle connecting authenticating error>;</idle>替代整型枚举 + 大块 union - 绝不裸露
union或原始指针;std::variant自动调用构造/析构,你省掉的不是几行代码,是整个生命周期管理责任
std::visit 是唯一推荐的状态分发方式
用 std::get<t>(state)</t> 或 holds_alternative<t></t> 手动分支,等于把类型检查从编译期退回到运行时,还容易漏分支——std::visit 强制你覆盖所有可能类型,少一个编译直接报错。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
实操建议:
- 写
std::visit([](const auto& s) { /* ... */ }, state),配合if constexpr做编译期类型分发 - 别在 lambda 里写长逻辑;把它拆成独立函数,比如
handle(const Connecting& s)、handle(const Authenticating& s) - 如果某些状态共享行为(如都需记录日志),提取公共函数,不要在 visit 内部重复写
std::cout
transition 的实现必须同步更新 variant 和其内部数据
容易踩的坑:只改了状态变量值,但没正确构造新状态对象,导致 std::variant 持有未初始化或已析构的成员;或者用 std::get_if<connecting>(&state)</connecting> 判断后直接赋值,却忘了旧状态的析构时机不可控。
实操建议:
- 状态变更一律用赋值:
state = Connecting{new_sock_fd};或state = std::in_place_type<connected>, remote_addr;</connected> - 避免原地修改:不要对
std::get<connecting>(state)</connecting>返回的引用做字段级更新,这绕过了std::variant的类型安全机制 - 若需保留部分旧状态数据(比如从
Connecting进入Connected时复用 socket 句柄),在构造新状态时显式传入,而不是靠“共享内存”
最常被忽略的一点:std::variant 不是万能胶水,它解决的是“状态数据异构”问题。如果你的所有状态都只存一个整数或都无数据,用 enum class + switch 更轻量;强行套 std::variant<int int></int> 只会增加二进制体积和访问开销。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










