用 std::variant 写状态机是唯一能避免运行时崩溃的方案:std::get<T> 危险因抛 bad_variant_access,须用 std::visit 显式覆盖所有状态;状态 struct 必须无 optional/裸指针/非 noexcept 析构;状态更新需原子赋值。

用 std::variant 写状态机,不是“换种写法”,而是当状态携带不同数据时,唯一能避免 std::bad_variant_access、字段误访问、析构遗漏和运行时崩溃的方案。
为什么 std::get(state) 是危险操作
直接用 std::get<Connecting>(state) 强制取值,等于把类型安全交给运气:一旦 state 当前实际是 Idle 或 Error,程序立刻抛 std::bad_variant_access —— 这是运行时错误,测试难覆盖,线上才暴露。
- 它绕过编译器对分支完整性的检查,退化回传统
enum + switch的漏处理风险 -
std::get适合临时调试或已明确断言过类型的极少数场景(如状态转换入口),绝不该出现在主逻辑分支中 - 若真需要按类型取指针做轻量判断,改用
std::get_if<Connecting>(&state),返回nullptr表示不匹配,安全但需手动判空
std::visit 必须显式覆盖所有状态类型
std::visit 的核心价值不是“方便分发”,而是让漏写某个状态分支成为编译错误。泛型 lambda([](const auto& s) { ... })配合 if constexpr 看似简洁,但新手常因编译失败删掉分支、补个 else 了事,结果又回到运行时不可控。
- 推荐写法是显式列出所有状态:用
overloaded{ [](const Idle&) { ... }, [](const Connecting& s) { use(s.sock); }, ... }(overloaded是常见辅助模板,几行即可定义) - 每个 lambda 参数必须为
const T&(非auto&&),否则if constexpr在部分特化下可能失效 - 新增状态时,编译器会报错指出哪个 lambda 缺失 —— 这才是你该依赖的“强制审查机制”
状态 struct 设计的三个硬约束
每个状态 struct 不是容器,而是该状态的“数据契约”。违反以下任一约束,就会引入共享字段、默认构造无意义、或生命周期失控等隐患。
立即学习“C++免费学习笔记(深入)”;
- 禁止出现“可能为空”的成员:比如在
Connecting里塞std::optional<socket_t>,而它本该由构造函数强制传入 ——Connecting(socket_t s, std::string ip)才是正确签名 - 空状态(如
Idle)必须定义为struct Idle {};,而非std::monostate:std::monostate语义模糊,后续加字段(如Idle{bool was_recently_active})会破坏二进制兼容性 - 禁止放入裸指针(
std::variant<A*, B*>):这等于放弃std::variant自动析构的优势,资源管理责任被踢给上层,极易泄漏
状态更新必须原子且不可拆分
状态变更不能靠“先改字段再改枚举”,也不能在 std::visit 里边读边写。正确做法是:一次构造新状态,一次赋值替换整个 std::variant。
- 例如从
Connecting转到Connected,不要写state = Connected{session_id, timer};—— 这没问题;但绝不要写std::get<Connecting>(state).sock = new_sock;,因为此时state可能根本不是Connecting - 若转换逻辑复杂(如需根据当前状态计算新状态),把整个逻辑封装进一个纯函数:
State next_state(const State& current, const Event& e) { return std::visit(...); },然后state = next_state(state, e); - 注意:
std::variant赋值会触发旧状态析构、新状态构造,确保中间态不会残留未析构对象 —— 这是它比裸union和手动enum安全的根本原因
最易被忽略的一点:状态 struct 的析构函数必须是 noexcept(或默认隐式满足)。如果某个状态持有 std::vector 或 std::string,它们的析构天然 noexcept;但若你手动写了可能抛异常的析构函数,std::variant 的赋值/移动操作会编译失败 —— 这不是 bug,是设计强制你面对资源清理的确定性。



















