应使用 std::variant 替代裸 union,它类型安全、自动管理生命周期;声明需显式列出非重复的可选类型,初始化支持默认、直接和 in_place_type 三种方式;读取必须用 std::get(可能抛异常)或更推荐的 std::visit(编译期全覆盖、无异常)。

直接用 std::variant 替代裸 union,它能自动管理类型生命周期、禁止非法访问,并在编译期约束可选类型——这是 C++17 引入类型安全联合体的唯一正统方式。
如何声明和初始化 std::variant
std::variant 是一个模板类,必须显式列出所有允许的类型。它不接受重复类型,也不支持 void 或引用类型(但可存 int& 的包装如 std::reference_wrapper)。
常见错误:写成 std::variant(重复类型编译失败),或试图 std::variant(auto 不合法)。
初始化方式有三种:
立即学习“C++免费学习笔记(深入)”;
- 默认构造:仅当首个类型有默认构造函数时才可行,例如
std::variant默认构造为int{} - 直接初始化:如
std::variant,编译器按参数类型推导并调用对应分支v{42}; - 使用
std::in_place_type_t显式指定:如std::variant<:string double> v{std::in_place_type<:string>, "hello"},适合需要带参构造的类型
如何安全读取 variant 中的值
不能像裸 union 那样直接 reinterpret_cast 或强制访问。必须通过 std::get 或 std::visit —— 否则触发未定义行为(UB)。
std::get 在运行时检查当前存储类型是否为 T,若不是则抛出 std::bad_variant_access。适用于已知类型且愿意处理异常的场景。
std::visit 是更推荐的方式,它把类型分发逻辑交给编译器,天然覆盖所有可能分支,且无异常开销:
std::variantv = 3.14; std::visit([](const auto& x) { using T = std::decay_t ; if constexpr (std::is_same_v ) { std::cout << "int: " << x << "\n"; } else if constexpr (std::is_same_v ) { std::cout << "double: " << x << "\n"; } else if constexpr (std::is_same_v ) { std::cout << "string: " << x << "\n"; } }, v);
注意:lambda 必须是泛型(auto&),且内部要用 if constexpr 做编译期分支,否则会实例化所有分支导致编译失败(比如对 int 调用 .size())。
如何判断当前存储的是哪种类型
用 v.index() 获取当前类型的零基序号(从 0 开始),或用 v.valueless_by_exception() 判断是否因异常中途构造失败(极少见,通常发生在某类型移动构造抛异常时)。
更实用的是 std::holds_alternative,返回 bool 表示是否正存储 T:
- 比
v.index() == 1更可读、不易出错 - 可用于条件逻辑,例如:
if (std::holds_alternative<:string>(v)) { /* 安全 get */ } - 但它只是“快照”,无法防止其他线程并发修改
v;多线程下仍需额外同步
std::variant 和 union 的关键差异与陷阱
它不是语法糖,底层实现通常包含一个类型标签 + 内联缓冲区(类似 std::optional),所以大小是各类型大小的最大值加上一个字节(用于 tag)。这意味着:
- 大对象(如含 1KB 缓冲的结构体)会使整个
std::variant变得臃肿,慎用于高频小对象场景 - 移动语义被正确实现:移动后源
variant进入 valueless 状态(v.valueless_by_exception() == true),再次访问前必须重赋值 - 不支持
constexpr构造(C++20 起部分支持),若需编译期确定类型,请考虑std::variant配合if constexpr,而非运行时std::visit
真正难处理的点不在语法,而在于类型集合设计:一旦定义了 std::variant,后续新增类型就必须改所有 std::visit 分支和 std::holds_alternative 判断——这本质上是一种有限制的代数数据类型(ADT),扩展性弱于动态类型系统,但换来的是编译期安全和零运行时成本。










