std::variant 是 C++17 引入的类型安全联合体,运行时明确持有且仅持有一种预定义类型,自动管理构造/析构、禁止隐式转换,并通过 std::visit 或 std::get 安全访问。

std::variant 是 C++17 引入的类型安全联合体(type-safe union),它能在一个对象中存储多种不同类型中的某一种,并在运行时明确知道当前存的是哪个类型,彻底避免了传统 union 的未定义行为和手动管理类型的麻烦。
核心特点:安全、明确、可访问
与裸 union 不同,std::variant 自动管理构造/析构、禁止隐式转换、提供编译期类型检查和运行时状态查询。它不是“多个值共存”,而是“**一个值,但可能是其中某一种类型**”。
- 只能持有模板参数列表中声明的类型之一(如
std::variant<int std::string double></int>) - 默认初始化为第一个类型的默认值(如
int{}),也可用std::monostate表示空状态 - 访问前必须确认当前持有的类型,否则抛出
std::bad_variant_access
基本用法:构造、赋值、获取值
构造方式灵活,支持直接初始化、拷贝/移动、甚至聚合初始化(C++20 起):
std::variant<int, std::string> v1 = 42; // 推导为 int
std::variant<int, std::string> v2{"hello"}; // 推导为 string
std::variant<int, std::string> v3 = std::string{"world"}; // 显式构造
v1 = std::string{"changed"}; // 赋值会自动销毁旧值、构造新值
获取值推荐使用 std::get<T>(v) 或 std::get<I>(v)(I 为索引),但需确保类型匹配:
立即学习“C++免费学习笔记(深入)”;
-
std::get<int>(v1)—— 按类型取,若当前不是int则抛异常 -
std::get<0>(v1)—— 按索引取(int是第 0 个),同样校验类型 -
std::holds_alternative<std::string>(v1)可先判断是否持有某类型
安全访问:std::visit 是最佳实践
最推荐的方式是用 std::visit 配合 lambda 或函数对象——它自动分发到当前实际类型,类型安全且无需手动判断:
std::variant<int, std::string, double> 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 << '\n';
} else if constexpr (std::is_same_v<T, std::string>) {
std::cout << "string: " << x << '\n';
} else if constexpr (std::is_same_v<T, double>) {
std::cout << "double: " << x << '\n';
}
}, v);
这种写法利用了 C++17 的 constexpr if,在编译期裁剪分支,零开销且类型完整覆盖。也可以用重载的函数对象(如 struct Visitor { void operator()(int){} void operator()(const std::string&){} })。
常见陷阱与注意事项
-
std::variant不允许包含自身类型(如std::variant<int, std::variant<int>>),但可通过std::unique_ptr间接实现递归结构 - 所有备选类型必须满足可析构、可移动(大部分情况也要求可复制),且不能是抽象类或数组类型
- 默认构造的 variant 总是初始化为第一个类型的默认值;若不想默认初始化任何类型,可用
std::variant<std::monostate, T1, T2> - 注意
std::get_if<T>(v)返回指针,可用于安全试探性访问(不抛异常)










