std::variant访问前必须用std::holds_alternative或std::visit确认类型,否则std::get<T>会直接调用std::terminate崩溃;std::monostate不可std::get,仅能通过std::holds_alternative判断;其内存布局为最大备选项大小加1字节type index。

std::variant 访问值前必须确认当前持有类型
直接用 std::get<T>(v) 强制取值,一旦 v 当前不存 T 类型,程序立刻调用 std::terminate——不是抛异常,是直接崩溃。这是最常踩的坑,尤其在多分支逻辑里忘了检查就硬取。
正确做法永远先用 std::holds_alternative<T>(v) 判断,或统一走 std::visit。比如处理配置项可能为 int、std::string 或 std::monostate:
std::variant<int, std::string, std::monostate> cfg = get_config();
if (std::holds_alternative<int>(cfg)) {
int val = std::get<int>(cfg); // 安全
}std::visit 的 lambda 捕获需显式声明,否则无法访问外部变量
写 std::visit([](auto&& x) { /* ... */ }, v) 时,lambda 默认无捕获,外面的 std::vector<int>& result 这类变量根本进不去。编译报错通常是 “use of undeclared identifier”,但错误位置指向 std::visit 调用行,容易误判。
解决方法只有两种:要么把 lambda 写成带捕获列表的完整形式,要么把逻辑拆到命名函数对象里。推荐前者,简洁可控:
立即学习“C++免费学习笔记(深入)”;
std::vector<int> result;
std::visit([&result](auto&& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, int>) {
result.push_back(x);
}
}, v);-
[&]捕获全部外部引用(注意生命周期) -
if constexpr是关键,编译期分发,避免运行时类型擦除开销 - 不能用普通
if,否则所有分支都得能编译通过
std::monostate 用来表示“空状态”,但不能用 std::get<std::monostate> 直接取
很多人以为 std::monostate 是个普通类型,试图 std::get<std::monostate>(v) 获取它。其实它只是占位符,语义上代表“什么都没有”,标准库禁止对它调用 std::get——会静态断言失败(编译不过),错误信息类似 static_assert failed due to requirement 'std::is_constructible_v<std::monostate>'。
判断是否为空,唯一可靠方式是:std::holds_alternative<std::monostate>(v)。常见场景是可选配置:
using Config = std::variant<std::string, std::monostate>
Config cfg = parse_config(); // 可能解析失败,设为 std::monostate
if (!std::holds_alternative<std::monostate>(cfg)) {
auto& s = std::get<std::string>(cfg); // 此时才安全
}和 union / void* 相比,std::variant 的内存布局和性能开销在哪
std::variant 在内存上等价于最大备选项加一个字节(用于存储 type index),没有额外指针或堆分配。它比裸 union 多 1 字节,比 void* + 手动类型管理更安全,也比 std::any 轻量得多(后者必有堆分配)。
但要注意两点实际影响:
- 构造/赋值开销略高:每次变更为新类型,旧值要析构、新值要就地构造(noexcept 要求严格)
- 不能存放非移动构造/非复制构造类型(如
std::mutex),除非你手动特化其std::variant_alternative - 调试时查看值不方便:GDB/LLDB 对
std::variant支持有限,经常只显示 type index,得靠std::visit打印
真要极致性能且类型确定,裸 union 仍有效;但只要涉及类型切换逻辑、需要 RAII 或跨模块传递,std::variant 的安全性收益远大于那 1 字节和轻微构造开销。
最麻烦的是嵌套 variant 和自定义访问器——这时候连 std::visit 都得写两层,type index 容易算错,建议封装一层 helper 函数再用。










