std::variant 是编译期+运行期双重保障的类型化联合体,核心在于内置标签(index)实现安全访问;std::visit 是唯一能穷举所有类型的真正安全访问方式,而 std::get 易因人为误判导致崩溃。

直接说结论:std::variant 不是“模拟”安全联合体,它本身就是编译期+运行期双重保障的类型化联合体——只要不用 std::get<t></t> 强行越界访问,就不会崩溃;只要类型列表合法,就不会有未定义行为。
为什么 std::variant 能替代 union 且不崩溃
根本原因在于它带“标签”(tag):每个 std::variant 实例内部隐式存储一个 index(),标识当前激活的是第几个类型。这个索引在构造、赋值、移动时自动更新,且所有访问入口(std::get、std::holds_alternative、std::visit)都依赖它做校验。
-
std::get<t>(v)</t>在运行时检查v.index()是否匹配T的位置,不匹配就抛std::bad_variant_access -
std::holds_alternative<t>(v)</t>只读取 index 并查表,零开销、无异常 - 默认构造总是初始化第一个可默认构造的类型,不存在“未初始化”状态
- 非平凡类型(如
std::string)的构造/析构由std::variant自动调度,无需 placement new
std::get 的坑:看似简单,实则最危险
很多开发者以为 std::get<t></t> 是首选访问方式,但它要求你**100% 确定当前类型**——而这种确定性往往靠人肉判断,极易出错。
- 写
if (std::holds_alternative<int>(v)) { std::get<int>(v); }</int></int>看似安全,但中间若被其他线程或函数修改了v,就会崩 - 多个
std::get连续调用时,每次都要重新查 index,性能不如一次std::visit - 模板参数
T必须精确匹配(std::get<const int></const>会失败,得用std::get<int></int>) - 对 const variant 只能用
std::get<const t></const>或std::get<t></t>(后者返回 const T&),别写错引用限定符
std::visit 才是真正安全的访问方式
std::visit 不是语法糖,它是唯一能**穷举所有可能类型分支**并由编译器强制覆盖的机制。只要你写的 lambda 或访客对象能处理全部类型,就不可能漏 case、不可能越界。
- lambda 参数必须是泛型(
auto&或const auto&),否则编译失败——这倒逼你写出可扩展逻辑 - 支持多个
std::variant同时传入,例如std::visit(f, v1, v2),自动做笛卡尔积匹配(要求所有组合都有对应重载) - 若漏掉某个类型,编译器报错类似:
no matching function for call to 'visit(...)' — candidate expects 3 overloads, 4 provided - 返回值类型由所有分支统一推导,不一致会编译失败,避免隐式转换陷阱
容易被忽略的底层约束
不是所有类型都能塞进 std::variant,这些限制不是 bug,而是安全边界的体现:
- 禁止直接存引用(
int&)、数组(char[10])、void、不完整类型——可用std::reference_wrapper<t></t>替代引用语义 - 类型列表中不能有重复类型(
std::variant<int int></int>编译失败),否则index()无法唯一标识 - 内存布局按最大类型对齐+大小分配,但不保证与 C union 二进制兼容——别试图
memcpy到裸指针 - 递归定义需间接:不能写
std::variant<int expr></int>(其中Expr就是该 variant),得用std::unique_ptr或std::shared_ptr包一层
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











