std::variant 是 c++17 引入的类型安全联合体,本质是“只能持有一种类型的容器”,通过自动管理构造/析构/赋值并强制类型检查访问(如 std::get、std::visit)避免裸 union 的未定义行为。

std::variant 是什么,为什么比裸 union 安全
std::variant 是 C++17 引入的类型安全联合体,本质是一个“只能持有一种类型的容器”。它内部自动管理当前活跃成员的构造、析构和赋值,彻底规避裸 union 手动调用构造函数/析构函数的错误风险。比如你写 union { std::string s; int i; };,直接赋值 s = "hello" 后再读 i —— 这是未定义行为;而 std::variant<:string int></:string> 强制你通过 std::get 或 std::visit 访问,且访问前会检查当前持有类型。
如何正确初始化和修改 std::variant
初始化必须明确指定哪个分支被激活,不能留空:
- 直接初始化:
std::variant<int std::string> v = 42;</int>→ 持有int - 使用
std::in_place_type_t显式构造(尤其对非默认构造类型):std::variant<:string double> v(std::in_place_type<:string>, "hi");</:string></:string> - 赋值时会自动销毁旧值、构造新值:
v = std::string("new");是安全的;但v = 3.14;会先析构 string,再构造 double - 禁止隐式转换到歧义类型:若
std::variant<int long></int>,写v = 5编译失败(二者都可接受),必须显式转成int(5)或long(5)
访问 variant 的三种方式及陷阱
不能像裸 union 那样“强行 reinterpret”,必须走类型检查路径:
-
std::get<t>(v)</t>:要求当前确实持有T,否则抛std::bad_variant_access。适合已知状态的场景,比如刚赋过值后立刻取 -
std::get_if<t>(&v)</t>:返回T*,若不匹配则返回nullptr,适合需要判空的逻辑 -
std::visit([](auto&& x) { ... }, v):最推荐,类型安全且支持所有分支统一处理;注意 lambda 参数必须能覆盖variant所有类型,否则编译失败 - 常见错误:对空 variant(如
std::monostate开头)直接std::get;或 visit 中漏掉某个类型分支,导致编译不过
性能与兼容性注意事项
std::variant 不是零开销抽象:
立即学习“C++免费学习笔记(深入)”;
- 大小至少为最大分支 size + 对齐 padding + 1 字节 type index(通常 1–8 字节,取决于分支数)
- 每次赋值/构造都有分支判断和可能的析构/构造开销,高频小对象场景(如每帧百万次)需实测对比裸 union
- 不能用于 POD 类型集合(如
std::variant<int float></int>是 POD,但含std::string就不是),影响 memcpy 和静态初始化 - MSVC 19.14+、GCC 7.2+、Clang 5.0+ 支持完整特性;老编译器可能缺
std::visitSFINAE 友好实现
真正麻烦的是嵌套 variant 或和 std::optional 混用时的状态组合爆炸——这时候别硬扛,该用 class 封装就封装,variant 的价值在清晰表达“互斥有限选项”,不在模拟任意复杂状态机。









