std::variant 解决传统 union 类型不安全问题,通过内置类型标签实现安全访问;支持 std::get、std::get_if、std::holds_alternative 安全读写,并借助 std::visit 实现类型匹配分发。

std::variant 能解决什么问题
传统 union 允许在一块内存里存不同类型的值,但不记录当前实际存的是哪种类型,访问错误类型会直接导致未定义行为。比如你写了 union { int i; double d; },然后往里面写了 i = 42,却读 d,程序就崩了——编译器不管,运行时也不报错,只等出事。
std::variant 把类型信息“带在身上”,每次赋值或修改都会更新内部的类型标签,读取前还能用 std::holds_alternative 或 std::get_if 安全检查,从根本上堵住误读漏洞。
怎么安全地读写 std::variant 的值
不能像 union 那样直接点成员或强制转型。必须通过标准接口操作,否则编译失败(这是好事)。
- 写入:直接赋值,例如
v = 42、v = 3.14、v = std::string{"hello"},v会自动切换到对应类型 - 读取单个类型:用
std::get<T>(v)—— 如果当前不是T类型,抛std::bad_variant_access - 安全读取:先用
std::get_if<T>(&v)拿指针,返回nullptr表示当前不是T,否则解引用访问 - 类型判断:用
std::holds_alternative<int>(v)得到bool
std::variant<int, std::string> v = "abc";
if (std::holds_alternative<std::string>(v)) {
std::cout << std::get<std::string>(v); // ok
} else {
std::cout << std::get<int>(v); // 不会执行
}
std::visit 是怎么配合 variant 一起用的
当你要对多种可能类型做不同处理(比如打印、序列化、计算),逐个 if/else + holds_alternative 写起来啰嗦还容易漏分支。std::visit 提供了一种类型安全的“分发”机制,它会在运行时根据 variant 当前实际类型,自动调用匹配的重载函数。
立即学习“C++免费学习笔记(深入)”;
- 参数必须是可调用对象(lambda、函数对象、普通函数),且每个重载都得覆盖
variant所有可选项 - 如果漏掉某个类型,编译失败,而不是运行时报错
- 支持多个
variant同时 visit(C++17 起),适合组合场景
std::variant<int, double, std::string> v = 3.14;
std::visit([](const auto& x) {
using T = std::decay_t<decltype(x)>;
if constexpr (std::is_same_v<T, int>) {
std::cout << "int: " << x;
} else if constexpr (std::is_same_v<T, double>) {
std::cout << "double: " << x;
} else if constexpr (std::is_same_v<T, std::string>) {
std::cout << "string: " << x;
}
}, v);
要注意的边界和开销
std::variant 不是零成本抽象。它比裸 union 多占至少一个字节(用于存储类型索引),而且构造/赋值/析构都涉及分支判断和可能的内存操作。
- 如果所有备选类型都是 trivial(如
int、float),variant本身也是 trivial 的;但只要有一个非 trivial 类型(比如std::string),它就会带上默认构造、析构等逻辑 - 不能存引用、数组、
void、抽象类等无法实例化的类型 - 不能包含自身(
std::variant<int, std::variant<...>>不合法),需要用std::unique_ptr或间接方式绕过 - 移动语义正常工作,但注意某些类型(如
std::string)移动后状态不确定,别在visit里反复用同一个变量多次访问
真正关键的是:别把它当成“万能容器”滥用。如果类型组合固定、逻辑清晰,variant 是极好的选择;但如果类型太多、嵌套太深、或者需要动态增删类型,该用多态或其它设计模式就别硬扛。








