std::variant 是类型安全的“多选一”容器,编译期确定可存类型,访问需显式处理每种可能;它比 union 安全但略重,c++17 起支持,应优先用 std::visit 安全读取,配合 std::optional 可表达“可能为空”,且需确保所含类型移动操作为 noexcept。

std::variant 是什么,为什么不能直接用 union
它是一个类型安全的“多选一”容器,编译期就确定能存哪些类型,访问时必须显式处理每种可能——不像 union 那样允许你随便读一个未写入的成员,一读就未定义行为。
常见错误现象:union 里写了 int,却去读 float 字段,程序可能跑得“好像没问题”,但其实随时崩溃或返回垃圾值;std::variant 强制你用 std::visit 或 std::get 前先确认当前存的是谁。
- 使用场景:需要在单个变量里动态切换几种固定类型(比如配置项可能是
int、std::string或bool) - 性能影响:比裸
union略重一点——多 1–2 字节存 type index,访问时有分支开销,但现代 CPU 分支预测很准,实际几乎不可测 - 兼容性:C++17 起支持,MSVC 15.3+、GCC 7.0+、Clang 4.0+ 都行
怎么安全地读取 std::variant 的值
不能像 union 那样直接点字段;必须用 std::get(指定类型)或 std::visit(穷举所有可能)。
容易踩的坑:std::get<int>(v)</int> 在 v 实际存的是 std::string 时会抛 std::bad_variant_access 异常——这反而是好事,总比静默读错强。
立即学习“C++免费学习笔记(深入)”;
- 推荐优先用
std::visit,尤其类型多于 2 个时,避免一堆if (holds_alternative<...>)</...> -
std::get<t></t>适合你 100% 确定当前类型(比如刚用std::holds_alternative<t></t>检查过) - 别用
std::get_if<t></t>后不检查返回指针是否为nullptr——这是空指针解引用高发区
示例:
std::variant<int, std::string> v = "hello";
std::visit([](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, std::string>) {
std::cout << "string: " << x;
}
}, v);
std::variant 和 std::optional 组合用的典型模式
很多真实场景不是“非此即彼”,而是“可能是 A、B、C,也可能啥都没有”——这时别硬塞一个“空类型”进去,直接套一层 std::optional<:variant b c>></:variant> 更清晰。
常见错误现象:有人定义 std::variant<a b c std::monostate></a> 来模拟“空”,结果每次访问前都要多写一次 std::holds_alternative<:monostate></:monostate>,逻辑噪音大。
-
std::optional<:variant>></:variant>的has_value()直观表达“有没有值” - 嵌套后访问略麻烦,建议封装成小函数,比如
get_if_int(const auto& optv) - 注意:
std::variant本身不允许含多个相同类型(std::variant<int int></int>编译失败),但std::optional<int></int>和int可以共存
移动语义和异常安全性要注意什么
std::variant 默认构造、赋值、交换都满足强异常安全(要么全成功,要么不变),但前提是它包含的所有类型也都满足——如果你塞了个可能抛异常的移动构造函数进去,整个 variant 的赋值就可能中途失败。
容易被忽略的地方:自定义类型放进 std::variant 前,务必确认它的移动操作是否 noexcept。否则 std::variant 的某些操作(比如 swap)会退化为拷贝+析构,性能掉一截。
- 检查方式:看类型是否有
T(T&&) noexcept构造函数 - 如果类型不满足,又不想改它,可以用
std::unique_ptr<t></t>包一层,移动变成本质上的指针复制 - 别依赖
std::variant自动帮你做深拷贝——它只管自己那块内存,内部类型的资源管理还是你自己负责








