该用 std::variant 当且仅当状态间共享字段少于50%;否则应选 enum class + 单一 struct。

直接用 std::variant 写状态机,只有当每个状态携带**结构差异显著的数据**时才真正值得;否则就是徒增编译开销、破坏可读性、还容易写出运行时崩溃的代码。
什么时候该用 std::variant 而不是 enum class
核心判断就一条:看状态之间“共享字段是否少于 50%”。
- Connecting 需要
std::string address和int retry_count,Connected 需要int sockfd和std::chrono::seconds timeout,Idle 真的什么也不需要 → 适合std::variant - 所有状态都带
std::string id、uint64_t timestamp、bool is_active→ 别硬套std::variant,老实用enum class+ 单一 struct - 用
std::optional塞满一个大 struct 的写法,会导致访问前必须反复判空、默认构造无意义、内存浪费 —— 这正是std::variant要帮你避开的坑
std::visit 必须覆盖所有分支,且不能靠 else 补漏
漏写一个状态分支,编译器会直接报错。这不是缺陷,是保护机制;但新手常因此删掉 else if 改成 else,结果把编译期检查退化成运行时崩溃。
- 必须用
std::decay_t提取真实类型,否则引用类型匹配失败 - 必须用
if constexpr,不用的话,哪怕当前是Connected,编译器仍会尝试编译对Connecting::address的访问,直接报错 - 参数推荐用
const auto&,避免auto&&在含std::string或std::vector的状态里意外触发移动语义 - 更安全的写法是显式列出所有状态,比如用
overloaded{ [](const Idle&) { }, [](const Connecting& s) { /* ... */ } },新增状态时编译器立刻指出缺哪个 lambda
状态 struct 设计的三个硬约束
每个状态 struct 不是容器,而是该状态的“数据契约”;违反任一约束,就会引入字段误访问、析构遗漏或生命周期失控。
- 禁止出现
std::optional或裸指针:比如Connecting里不该有std::optional,而应强制通过构造函数传入 ——sock_fd Connecting(int fd, std::string ip) - 空状态必须定义为
struct Idle {};,别用std::monostate:后者语义模糊,后续加bool was_recently_active会破坏二进制兼容性 - 禁止在
std::variant里放std::unique_ptr或* T*:这等于放弃自动析构,资源管理责任被踢给上层,极易泄漏
最易被忽略的一点:状态更新必须原子赋值,比如 state = Connected{fd, timeout};绝不要用 std::get 去原地修改字段 —— 这绕过了 std::variant 的类型安全边界,且旧状态的析构时机完全失控。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











