std::variant 是类型安全的可变容器,强制记录当前持有的类型,不可当作普通 union 直接内存读写;必须用 std::holds_alternative 判断、std::get 或 std::get_if 安全访问、emplace 或赋值切换类型。

std::variant 是什么,为什么不能当普通 union 用
std::variant 不是 union 的语法糖,而是一个类型安全的“可变容器”——它在任意时刻只持有其中一种类型,并且**强制记录当前实际持有的类型**。直接按 memory layout 当 union 读写(比如 reinterpret_cast 或 memcpy)会触发未定义行为,因为 variant 内部有 type index 和可能的就地构造/析构逻辑。
常见错误现象:std::get<int>(v)</int> 报 std::bad_variant_access;或读取非当前活跃类型的值看似“成功”,实则踩内存、结果不可预测。
- 必须用
std::holds_alternative<t>(v)</t>先判断当前是否持有 T 类型 - 获取值必须用
std::get<t>(v)</t>(静态类型检查)或std::get_if<t>(&v)</t>(返回指针,安全但需判空) - 赋值或修改请用
v = T{...}或v.emplace<t>(...)</t>,不要手动 placement new
如何安全地初始化和切换 variant 中的类型
variant 构造时默认调用第一个备选类型的默认构造函数(除非显式禁止)。若第一个类型无可默认构造,编译失败。切换类型不是“覆盖内存”,而是先析构旧值、再就地构造新值。
使用场景:状态机中的不同状态数据、解析 JSON 时的 number/string/bool/null 混合字段、事件系统中携带不同类型 payload。
立即学习“C++免费学习笔记(深入)”;
- 初始化:
std::variant<int std::string double> v = 42;</int>→ 持有 int - 切换:
v = std::string{"hello"};→ 自动析构 int,构造 string - 强制构造特定类型:
v.emplace<double>(3.14);</double>→ 跳过赋值运算符,直接调用 double 构造函数 - 避免隐式转换干扰:把
int放在long long前面,否则v = 42可能被推导为 long long
访问 variant 的三种方式及各自风险
不推荐裸用 std::get,尤其在运行时类型不确定时。std::visit 是最安全、最通用的访问方式,但写法略冗长;std::get_if 适合单类型试探;std::get 仅适用于编译期已知类型的分支(如 switch 表达式配合 index())。
-
std::get<int>(v)</int>:抛异常,不建议在无把握时用 -
auto* p = std::get_if<:string>(&v); if (p) use(*p);</:string>:安全,但要手动判空 -
std::visit([](const auto& x) { /* ... */ }, v);:类型安全,支持泛型处理,但 lambda 必须对所有备选类型都可调用(可用if constexpr分支) - 注意:lambda 参数类型是 const 引用,修改需用
std::get配合非 const 引用或指针,或改用std::visit的非常量重载
性能与 ABI 兼容性要注意什么
std::variant 的大小通常是所有备选类型 size 最大值 + sizeof(index),对齐按最大对齐要求。它不保证 trivially copyable,因此不能用于 memcpy、跨 DLL 边界传递或作为 C 接口参数。
- 小对象(如
int、bool)无额外开销;含 string/vector 等类型时,variant 占用空间大,但仍是栈分配,避免堆分配延迟 - 移动语义完整支持:move 构造/赋值会转移内部值(如 string 的 move),比拷贝快
- Windows MSVC 和 GCC/Clang 在 ABI 上基本一致,但若 variant 定义在头文件中被多个 TU 包含,确保所有地方看到完全相同的模板实参顺序和类型定义(尤其是别名展开后)
- 调试时注意:GDB/LLDB 对 variant 的显示支持有限,
print v.index()和print v可能只显示部分信息,必要时用std::visit打印
真正难的不是怎么写 variant,而是想清楚哪些类型该放进同一个 variant —— 类型太多会让 std::visit 分支爆炸,类型之间语义无关会破坏封装。宁可拆成两个 variant,也不要塞进一个“万能容器”。











