应仅在状态间结构差异大、共享字段少于50%时用std::variant实现状态机;否则徒增编译负担、分散逻辑且易致运行时崩溃。

直接用 std::variant 实现状态机,只在状态携带**结构差异大、共享字段少于 50%** 的数据时才真正值得;否则就是徒增编译负担、分散逻辑、还容易踩运行时崩溃的坑。
什么时候该用 std::variant 而不是 enum class
核心看字段是否“按状态分裂”:
- ConnectingState 需要
std::string address和int retry_count - ConnectedState 需要
int sockfd和std::chrono::seconds timeout - IdleState 真的什么也不需要 → 定义为
struct Idle {};,别塞std::monostate或空optional
这时硬套 enum class State { Idle, Connecting, Connected }; + 一个大 struct 塞满 std::optional<:string></:string>、std::optional<int></int>… 会导致:
- 每次访问前必须手动判
has_value(),漏一次就是未定义行为 - 内存浪费(所有 optional 都占空间,哪怕当前是
Idle) - 默认构造语义模糊(
State{}到底代表什么?)
std::visit 必须显式覆盖所有分支,且参数要用 const auto&
泛型 lambda 看似简洁,但写错就编译失败——这不是缺陷,是保护机制。常见错误是删分支、加 else 应付编译器,结果退化回运行时风险。
立即学习“C++免费学习笔记(深入)”;
正确写法必须同时满足三点:
- lambda 参数写成
const auto& s,避免auto&&触发意外移动(尤其当状态含std::string或std::vector) - 用
std::decay_t<decltype></decltype>提取真实类型,否则引用类型不匹配,std::is_same_v永远为 false - 每个
if constexpr分支对应一个确切状态类型,不许漏、不许合并、不许用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);状态转换不能靠字段级修改,必须原子赋值新对象
常见误操作:std::get<connecting>(state).address = new_addr;</connecting> —— 这绕过了 std::variant 的类型安全机制,且无法保证旧状态析构时机。
正确做法是:状态变更即构造新对象,一次性替换整个 std::variant:
- 用
state = Connecting{"127.0.0.1", 3};,而非修改已有字段 - 若需复用旧状态部分数据(比如从
Connecting进入Connected时保留 socket),应在构造Connected时显式传入,而不是依赖“共享内存” - 禁止在
std::variant中放裸指针(如std::variant<int std::string></int>),否则资源管理责任被踢给上层,极易泄漏
std::get<t></t> 是危险操作,仅限断言后临时调试
std::get<connecting>(state)</connecting> 一旦当前不是 Connecting,立刻抛 std::bad_variant_access —— 这是运行时错误,测试难覆盖,线上才暴露。
它绕过编译器对分支完整性的检查,退化回传统 switch 的漏处理风险。真需要轻量判断,改用:
-
if (auto* p = std::get_if<connecting>(&state)) { /* use *p */ }</connecting>,返回nullptr表示不匹配,安全但需手动判空 -
std::holds_alternative<connecting>(state)</connecting>仅作类型检查,不取值
主逻辑里绝不要出现 std::get,这是最常被忽略、也最容易引发线上崩溃的点。



















