应仅在状态数据互不兼容且共享字段少于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) 是常见错误起点。它看似简洁,但实际会触发编译失败或运行时崩溃。
立即学习“C++免费学习笔记(深入)”;
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 参数必须是
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(s)>;
if constexpr (std::is_same_v<T, Idle>) {
// do nothing
} else if constexpr (std::is_same_v<T, Connecting>) {
std::cout << "Connecting to " << s.address << "\n";
} else if constexpr (std::is_same_v<T, Connected>) {
std::cout << "Active on fd " << s.sockfd << "\n";
}
}, current_state);状态 struct 设计有三个硬约束,违反任一就会出问题
每个状态 struct 不是容器,而是该状态的“数据契约”:
- 禁止出现
std::optional或裸指针:比如Connecting里不该有std::optional<int> sock_fd,而应强制通过构造函数传入 ——Connecting(int fd, std::string ip) - 空状态必须定义为
struct Idle {};,别用std::monostate:后者语义模糊,后续加字段(如bool was_recently_active)会破坏二进制兼容性 - 禁止在
std::variant里放std::unique_ptr<T>*或T*:这等于放弃自动析构,资源管理责任被踢给上层,极易泄漏
状态更新必须原子赋值,绝不能改字段
常见误操作:std::get<Connecting>(state).address = new_addr; —— 这绕过了 std::variant 的类型安全机制,且无法保证旧状态析构时机。
- 状态变更即构造新对象,一次性替换整个
std::variant:用state = Connecting{"127.0.0.1", 3}; - 不要在
std::visitlambda 内部做状态变更,除非你明确接受副作用耦合;更推荐封装为State transition(const Connecting&, const Event&)函数,返回新状态,再统一赋值 - 确保原状态的析构函数在赋值前自动调用 —— 这是
std::variant唯一能帮你守住的底线
最易被忽略的一点:std::get 不是访问手段,而是调试/断言后的兜底操作;主逻辑里一旦出现,基本意味着设计已退化回传统 enum + switch 的脆弱模式。


















