std::variant 不能直接作容器,需嵌入 std::vector 等容器中;访问须用 std::visit、std::holds_alternative 或 std::get_if 避免 std::bad_variant_access;默认构造依赖首类型,推荐以 int 或 std::monostate 开头;类型列表须编译期确定,不适用于动态类型场景。

std::variant 不能直接当容器用,得套一层 std::vector
很多人一上来就想写 std::variant<int std::string double> data = ...</int> 然后往里塞一堆值——这只能存一个值,不是容器。真正要存多个不同类型的数据,必须把 std::variant 当作元素类型放进 std::vector 或其他标准容器里。
常见错误现象:std::variant 构造失败报错 no matching constructor,往往是因为忘了它只管单个值的类型安全,不负责聚合。
- 正确姿势是:
std::vector<:variant std::string double>></:variant> - 插入时用
std::in_place_type_t或直接构造:比如v.emplace_back(42)、v.emplace_back(std::string{"hello"}) - 别用
v.push_back(42)混合隐式转换——某些编译器(如 MSVC)在模板推导时可能选错分支,导致意外调用std::string构造函数
访问 std::variant 元素必须处理所有可能类型,否则运行时报 std::bad_variant_access
这是最常踩的坑:写了 std::get<int>(v[i])</int>,但当前元素其实是 std::string,程序直接 std::terminate。C++ 不做运行时类型宽容,std::get 是“断言式”访问。
使用场景:解析 JSON-like 结构、配置表、混合日志字段等需要动态判型的地方。
立即学习“C++免费学习笔记(深入)”;
- 安全做法是用
std::visit+ lambda,它强制你覆盖所有变体类型 - 如果只关心某几种类型,可用
std::holds_alternative<t></t>先判断再取,但漏判仍会崩 -
std::get_if<T>(&v)返回指针,空指针表示类型不匹配——比std::get安全,但需手动判空
类型列表顺序影响 std::variant 的默认构造和赋值行为
std::variant 默认构造时,会尝试默认构造其第一个类型。如果第一个类型不可默认构造(比如 std::string_view 或自定义无默认构造函数的类),整个 std::variant 就无法默认构造。
性能影响不大,但兼容性上容易栽跟头:比如从 std::variant<:string_view int></:string_view> 改成 std::variant<int std::string_view></int>,可能让原本能编译的代码突然报错。
- 若不需要默认构造,显式删掉:
std::variant<int, std::string> v = std::monostate{} - 把可默认构造的类型(如
int、std::monostate)放在列表最前面,是最省心的做法 -
std::monostate是个零开销占位符,专为解决“无默认类型”问题而生,推荐作为备选项加在类型列表末尾或开头
和 std::any、void* 比,std::variant 的类型集合必须编译期确定
如果你的需求是“运行时才知道有哪些类型”,比如插件系统动态加载任意类型的值,std::variant 就不合适——它的类型列表是模板参数,必须在编译时写死。
这时候该考虑 std::any(带运行时类型擦除)或更轻量的方案(比如用 ID 映射到 std::function 解析器)。
-
std::variant的优势在于零成本抽象:没有虚函数、没有堆分配、访问是纯内联的 - 但代价是灵活性受限——加一种新类型就得改模板参数、重新编译
- 调试时注意:
sizeof(std::variant<T...>)至少等于最大类型大小 + 对齐填充,比std::any小得多,也比手写 union 更安全









