std::variant是处理多态状态的唯一合理方式,因其支持编译期类型安全;std::get绕过检查易致运行时崩溃,而std::visit配合if constexpr可确保分支全覆盖、编译期报错,状态struct应精简字段、禁止共享冗余数据,且状态变更须原子赋值。

直接用 std::variant 定义状态类型,是处理“每个状态携带不同数据”的唯一合理方式;用 enum class + 成员变量堆叠只会让代码越来越脆、越来越难维护。
为什么 std::get(state) 是危险操作
它绕过编译期检查,运行时抛 std::bad_variant_access —— 你得在测试里撞上所有分支才能发现漏处理,而不是编译就报错。
- 一旦状态类型增删(比如加了
Reconnecting),std::get不会提醒你更新任何地方 -
std::get<:string>(state)</:string>在 state 实际存的是int时直接崩溃,毫无容错余地 - 它鼓励你在外面手动判类型再取值,退化成 if-else 套壳,完全浪费
std::variant的类型安全优势
std::visit 必须显式覆盖所有状态分支
泛型 lambda 配合 if constexpr 是最简写法,但新手常误删分支或加 else 兜底,结果又回到运行时错误。
- 正确写法:
std::visit([](const auto& s) { using T = std::decay_t<decltype>; if constexpr (std::is_same_v<t idle>) { /* */ } else if constexpr (std::is_same_v<t connecting>) { /* s.sock */ } }, state)</t></t></decltype> - 漏写任一状态 → 编译失败,这是设计意图,不是 bug
- 别用
auto&&当参数名(如[](auto&& x))然后在内部调x.some_method()—— 一旦某个状态没这个方法,编译就炸,且错误信息极难读
状态 struct 定义必须“只放它真正需要的字段”
共享字段不是美德,是耦合源头。一个 MovingState 里塞 error_code 或 timeout_ms,等于把逻辑漏洞刻进内存布局。
-
Idle真没数据?就写struct Idle {};,别用std::monostate—— 后者语义模糊,后续加字段时你会犹豫“这算不算破坏兼容?” -
Connecting存socket_t和std::string ip?没问题,std::variant会自动管理它们的构造/析构 - 禁止出现
std::optional<int> error_code;</int>这类字段 —— 如果某状态不需要 error,就让它根本不存在
最难绷的一点:很多人写完 std::variant<a b c></a> 就以为万事大吉,却忘了每次状态变更都该是原子赋值(state = Connecting{fd, "127.0.0.1"}),而不是先改 state.index() 再手动构造对象 —— 后者会留下中间不一致状态,尤其在异常路径下极易出事。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











