std::variant 是 C++17 引入的类型安全联合体,区别于 union:它自动管理构造/析构、支持运行时类型查询与安全访问(如 std::visit、std::get),要求备选类型可析构和可移动,禁止数组/引用/void 等类型。

std::variant 是什么,和 union 有什么区别
std::variant 是 C++17 引入的类型安全联合体,本质是一个“只能持有一种类型的容器”,编译期就限定可选类型集合,运行时能安全知道当前存的是哪个。它不是 union 的语法糖——原生 union 不调用构造/析构、不检查访问类型、容易 UB;而 std::variant 自动管理对象生命周期,提供 std::visit 和 std::get 等安全访问机制。
常见错误现象:直接对 std::variant 成员取地址或强制 reinterpret_cast;试图像 union 那样“覆盖写入”不同分支;忽略 valueless_by_exception 状态。
- 必须显式初始化(默认构造只对第一个可默认构造的类型生效)
- 所有备选类型必须满足可析构、可移动(部分场景还需可复制)
- 不支持数组、引用、
void、某些不完整类型
怎么安全获取 variant 中的值:get vs visit
std::get 直接按类型提取,但若当前不持有 T,抛出 std::bad_variant_access;std::get(v) 按索引提取,越界同样抛异常。适合你确定类型且愿意承担异常成本的场景。
std::visit 是更推荐的方式,尤其在泛型上下文中——它接受一个可调用对象(lambda、函数对象等),编译期生成所有分支的 dispatch 表,运行时根据当前 index 调用对应重载。
立即学习“C++免费学习笔记(深入)”;
std::variantv = "hello"; std::visit([](const auto& x) { using T = std::decay_t ; if constexpr (std::is_same_v ) { std::cout << "int: " << x; } else if constexpr (std::is_same_v ) { std::cout << "string: " << x; } else if constexpr (std::is_same_v ) { std::cout << "double: " << x; } }, v);
注意:
- lambda 参数必须能匹配所有备选类型(否则编译失败)
-
std::visit不支持“漏掉某个类型”的写法;若想 fallback,可用std::holds_alternative+ 手动分支 - 若 variant 处于
valueless_by_exception状态,std::visit会抛异常
泛型处理 variant:如何写一个通用的 to_string
写泛型转换函数的关键是避免为每个新类型重复实现,同时保持类型安全。推荐用 std::visit + lambda + 可变参数模板组合:
templatestruct to_string_visitor { std::string operator()(const Ts& t) const { return std::to_string(t); } std::string operator()(const std::string& s) const { return "\"" + s + "\""; } std::string operator()(const auto&) const { return "unknown"; } }; template
std::string to_string(const std::variant & v) { return std::visit(to_string_visitor {}, v); }
使用场景:
- 日志打印、配置序列化、调试输出
- 需要适配自定义类型时,在 visitor 中特化
operator()
容易踩的坑:
- 忘记给 visitor 加
const限定符,导致无法绑定 const variant - 在 visitor 中返回类型不一致(比如有的分支返回
std::string,有的返回void),编译失败 - 对
std::monostate或空状态没做处理,访问时报错
性能与兼容性要注意什么
std::variant 的空间开销是所有备选类型中最大 size 加上一个字节(用于存储 index);时间开销主要在 std::visit 的一次查表跳转,基本可视为 O(1),远优于动态类型如 std::any。
- MSVC 19.14+、GCC 7.2+、Clang 5.0+ 完整支持;老版本需谨慎
-
std::variant不支持constexpr构造(C++20 才部分支持) - 移动语义是安全的,但拷贝构造要求所有类型都可拷贝;若含不可拷贝类型(如
std::unique_ptr),只能移动
关键提醒:不要为了“省一个字节”把小类型(如 bool)和大类型(如 std::vector)塞进同一个 variant——index 存储本身不贵,但缓存局部性会被破坏,实际性能可能比两个独立变量还差。真正该关心的是访问模式是否集中、分支是否可预测。










