应仅在状态数据结构差异大且共享字段少于50%时用std::variant实现FSM;否则enum class更轻量。需严格遵循visit全覆盖、const auto&参数、if constexpr分支、原子状态赋值等规则。

直接用 std::variant 实现有限状态机(FSM)只有在状态携带**互不兼容、结构差异显著的数据**时才真正值得;否则,它只会让代码更重、编译更慢、错误更隐蔽。
什么时候该用 std::variant 而不是 enum class
核心判断依据是字段是否“按状态分裂”:如果各状态所需数据结构差异大,且共享字段少于 50%,std::variant 才发挥价值。
-
ConnectingState需要std::string address和int retry_count -
ConnectedState需要int sockfd和std::chrono::seconds timeout -
IdleState真的什么也不需要 → 定义为struct IdleState {};即可 - 若硬套
enum class State { Idle, Connecting, Connected }+ 一个大struct塞满std::optional字段,会反复判空、易漏初始化、内存浪费
std::visit 必须覆盖所有分支,且必须用 if constexpr + std::decay_t
漏写任一状态分支,编译直接失败——这不是 bug,是保护机制。但新手常因此退化成运行时错误。
- 参数必须用
const auto& s,避免意外触发移动语义(尤其含std::string或std::vector时) - 必须写
using T = std::decay_t<decltype>;</decltype>,否则引用类型不匹配,std::is_same_v<t connectingstate></t>永远为 false - 必须用
if constexpr,否则编译器会对所有分支尝试实例化,哪怕当前s是ConnectedState,也会因访问ConnectingState::address报错 - 别用
auto&&当参数名后直接调s.some_method()—— 某个状态没这个方法,编译炸且错误信息极难读
状态变更必须原子赋值,禁止原地修改或手动构造
每次状态跳转都应生成一个完整新对象,而非只改部分字段或先设 index 再填数据——否则极易留下中间不一致状态,尤其在异常路径下。
立即学习“C++免费学习笔记(深入)”;
- 正确写法:
state = Connecting{fd, "127.0.0.1"};或state = std::in_place_type<connected>, fd, timeout;</connected> - 禁止对
std::get<connecting>(state)</connecting>返回的引用做字段级更新,这绕过std::variant的类型安全和生命周期管理 - 若需复用旧数据(如从
Connecting进入Connected时保留 socket fd),应在构造新状态时显式传入,而不是靠“共享字段”或裸指针 - 绝不在
variant中存裸指针或std::unique_ptr以外的大对象(如std::vector<uint8_t></uint8_t>),否则栈空间暴涨、移动开销不可控
最难绷的一点:很多人定义完 std::variant<a b c></a> 就以为结束,却忘了 std::visit 的每个调用点、每个 transition 函数、甚至日志打印逻辑,都必须同步新增状态——三者脱节,std::bad_variant_access 只是迟早的事。



















