应仅在状态数据互不兼容且共享字段少于50%时用std::variant实现fsm;否则宜用enum class加单一struct,避免optional冗余、未定义行为及编译/运行时风险。

直接用 std::variant 实现有限状态机(FSM)只有在状态携带**互不兼容、共享字段少于 50%** 的数据时才真正值得;否则它不会简化逻辑,反而引入编译负担、分支遗漏风险和运行时崩溃可能。
什么时候该用 std::variant 而不是 enum class + 单一 struct
核心看字段是否“按状态分裂”:
-
ConnectingState需要std::string address和int retry_count -
ConnectedState需要int sockfd和std::chrono::seconds timeout -
Idle真的什么也不需要 —— 就该定义为struct Idle {};,别塞std::monostate或空std::optional
这时硬套 enum class State { Idle, Connecting, Connected }; + 一个大 struct 堆满 std::optional 字段,会导致:
- 每次访问前必须手动判
has_value(),漏一次就是未定义行为 - 内存浪费(所有
optional都占空间,哪怕当前是Idle) - 默认构造语义模糊(
State{}到底代表什么?)
std::visit 必须显式覆盖所有状态,且参数类型不能错
写成 std::visit([](auto& s) { ... }, state) 是常见错误起点。它看似简洁,但实际会触发编译失败或运行时崩溃。
- 参数必须是
const auto& s,避免auto&&在含std::string或std::vector的状态里意外触发移动语义 - 必须用
std::decay_t<decltype></decltype>提取真实类型,否则引用类型不匹配,std::is_same_v永远为false - 必须用
if constexpr,不用的话,哪怕当前s是ConnectedState,编译器仍会尝试编译对ConnectingState::address的访问,直接报错 - 禁止用
else补漏 —— 漏掉任一状态分支,就该让编译失败,这是你唯一能依赖的强制审查机制
正确写法示例:
std::visit([](const auto& s) {
using T = std::decay_t<decltype>;
if constexpr (std::is_same_v<t idle>) {
// do nothing
} else if constexpr (std::is_same_v<t connecting>) {
std::cout ) {
std::cout <h3>状态 struct 设计有三个硬约束,违反任一就会出问题</h3>
<p>每个状态 <code>struct</code> 不是容器,而是该状态的“数据契约”:</p>
<ul>
<li>禁止出现 <code>std::optional</code> 或裸指针:比如 <code>Connecting</code> 里不该有 <code>std::optional<int> sock_fd</int></code>,而应强制通过构造函数传入 —— <code>Connecting(int fd, std::string ip)</code>
</li>
<li>空状态必须定义为 <code>struct Idle {};</code>,别用 <code>std::monostate</code>:后者语义模糊,后续加字段(如 <code>bool was_recently_active</code>)会破坏二进制兼容性</li>
<li>禁止在 <code>std::variant</code> 里放 <code>std::unique_ptr<t>*</t></code> 或 <code>T*</code>:这等于放弃自动析构,资源管理责任被踢给上层,极易泄漏</li>
</ul>
<h3>状态更新必须原子赋值,绝不能改字段</h3>
<p>常见误操作:<code>std::get<connecting>(state).address = new_addr;</connecting></code> —— 这绕过了 <code>std::variant</code> 的类型安全机制,且无法保证旧状态析构时机。</p>
<ul>
<li>状态变更即构造新对象,一次性替换整个 <code>std::variant</code>:用 <code>state = Connecting{"127.0.0.1", 3};</code>
</li>
<li>不要在 <code>std::visit</code> lambda 内部做状态变更,除非你明确接受副作用耦合;更推荐封装为 <code>State transition(const Connecting&, const Event&)</code> 函数,返回新状态,再统一赋值</li>
<li>确保原状态的析构函数在赋值前自动调用 —— 这是 <code>std::variant</code> 唯一能帮你守住的底线</li>
</ul>
<p>最易被忽略的一点:<code>std::get</code> 不是访问手段,而是调试/断言后的兜底操作;主逻辑里一旦出现,基本意味着设计已退化回传统 <code>enum</code> + <code>switch</code> 的脆弱模式。</p></t></t></decltype>C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











