std::variant 是类型安全的联合体,核心区别在于自动管理活跃成员的构造与析构;裸 union 不跟踪状态、不调用构造/析构函数,易导致未定义行为。
什么是
,它和
本质区别在哪
是 C++17 引入的类型安全联合体,不是
的语法糖。核心区别在于:它**自动管理当前活跃成员的构造与析构**,而裸
不跟踪状态、不调用构造/析构函数,极易导致未定义行为。
常见错误现象:直接在
里放
或带非平凡析构函数的类型,读写错分支后程序崩溃或内存泄漏;
会阻止这类误用——编译器强制你处理所有可能类型,且切换值时自动调用旧值析构、新值构造。
必须显式初始化(如
),不能留空
不支持引用类型、数组类型、CV 限定类型(如
)作为备选项
底层仍用一块连续内存,但空间取各类型
最大值 + 对齐开销,比裸
稍大
如何安全访问
中的值:别只用
在运行时类型不匹配时抛出
,不是
编译错误
。线上环境一旦触发,进程直接终止——这不是“安全”,是延迟报错。
真正安全的做法是配合
检查,或更推荐使用
+ lambda 实现模式匹配:
立即学习
“
C++免费学习笔记(深入)
”;
C函数速查手册(CHM版)
C函数速查手册(CHM版)
下载
或
仅适合确定类型且能接受异常的调试场景
是零成本抽象,编译期分发,无虚函数开销
lambda 必须能处理
所有备选项,漏一个编译失败——这才是类型安全的体现
泛型场景下怎么让
更好用
泛型代码常需构造、转换、比较 variant,硬编码类型列表不可维护。关键点在于:把类型列表抽成模板参数包,再用
和折叠表达式驱动逻辑。
例如实现一个通用的“是否为某类值”的检查函数:
避免手动展开
自定义比较操作符(如
)必须对每对类型组合提供特化,否则默认行为是“仅当同类型且值相等”才返回 true
若需序列化,不要遍历
内部——它不公开存储细节;应通过
分发到各类型的序列化逻辑
容易被忽略的陷阱:异常安全与移动语义
的赋值和构造可能抛异常(比如
构造失败),而它的异常规范是
仅当所有备选项的对应操作都
。这直接影响你能否把它放进
并保证强异常安全。
若其中任一类型(如
)的移动构造可能抛异常,则
的移动赋值也非
用
赋值时,原 variant 会进入“值破坏”状态(
),此时调用
会抛异常——必须先用
检查
没有默认构造函数的类型(如仅含
构造函数的类)可作为备选项,但
本身无法默认构造,除非你显式提供默认值
复杂点不在语法,而在你是否意识到:每个备选项的异常行为、移动语义、生命周期,都在静默影响整个 variant 的可靠性。
std::variantunionstd::variantunionunionunionstd::stringstd::variantstd::variant v = 42; const intsizeofunionstd::variantstd::getstd::get(v) std::bad_variant_accessstd::holds_alternativestd::visitstd::variant v = "hello";
std::visit([](auto&& arg) {
using T = std::decay_t;
if constexpr (std::is_same_v) {
std::cout << "int: " << arg << "\n";
} else if constexpr (std::is_same_v) {
std::cout << "string: " << arg << "\n";
} else if constexpr (std::is_same_v) {
std::cout << "double: " << arg << "\n";
}
}, v);
std::get<0>(v)std::get(v) std::visitstd::variantstd::variantdecltypetemplate
constexpr bool is_one_of(const V& v) {
return (std::holds_alternative(v) || ...);
}
// 用法:is_one_of(v)
std::holds_alternative(v) || std::holds_alternative(v) || ... operator==std::variantstd::visitstd::variantstd::stringnoexceptnoexceptstd::vectorstd::vectorstd::variantnoexceptstd::movevalueless_by_exceptionstd::visitv.valueless_by_exception()explicitstd::variant