std::variant必须用std::visit安全访问,std::get仅适用于100%确定类型的断言场景;类型检查仅限编译期列表内类型,不支持继承;自定义类型须满足noexcept析构和可移动;性能介于if-else与虚函数之间。

std::variant访问值时必须用std::visit,不能直接取址
很多人试图像 std::any 那样用 std::get<t>(v)</t> 强制提取,但一旦类型不匹配,程序直接 std::bad_variant_access 终止——这不是异常可捕获的“错误”,而是未定义行为的前兆。核心原因:variant不保证当前持有哪个类型,std::get 是“断言式访问”,只适合你100%确定状态的极少数场景(比如刚构造完立刻读)。
正确路径只有 std::visit:它在编译期生成所有可能类型的分发逻辑,运行时安全跳转。常见误用是传入一个普通 Lambda 却漏写 auto 参数,导致编译失败:
std::variant<int, std::String> v = "hello"; // ❌ 编译不过:lambda参数类型不匹配所有分支 std::visit([](int x) { /* ... */ }, v); <p>// ✅ 正确:用auto推导,或显式重载operator() std::visit([](auto&& x) { std::cout << x; }, v);
- 如果需要区分不同类型的处理逻辑,优先写成
Struct visitor { void operator()(int) {...} void operator()(const std::string&) {...} };再传入std::visit(visitor{}, v) - 避免在 visitor 里做耗时操作——
std::visit本身开销极小,但逻辑复杂会拖慢热点路径 - 注意 const 与引用:传
v还是std::as_const(v),决定 visitor 能否修改内部值
std::holds_alternative检查类型时别当成运行时类型判断
std::holds_alternative<t>(v)</t> 看起来像动态类型检查,但它只是查 variant 当前是否恰好存着 T——不是“是不是某种基类”或“能不能转型为 T”。它和 std::get_if<t>(v)</t> 是一对:前者返回 bool,后者返回 T*(空指针表示不匹配)。
典型陷阱是想用它模拟多态行为:
立即学习“C++免费学习笔记(深入)”;
struct Base { virtual ~Base() = default; }; std::variant<DerivedA, DerivedB> v; // ❌ 错误期待:holds_alternative<Base> 永远为 false —— Base 根本不在 variant 的类型列表里 if (std::holds_alternative<Base>(v)) { ... }
- variant 的类型集合是编译期固定死的,不支持继承关系自动降级或提升
- 若真需要运行时多态,
std::variant不是替代品,该用虚函数或std::unique_ptr<base> -
std::holds_alternative合适的场景是:解析配置后做简单分流,比如std::variant<int double std::string></int>接口返回值
自定义类型放进variant前必须满足可析构、可移动
把自定义类塞进 std::variant<a b c></a>,编译器会静默拒绝某些类型——不是报错,而是让你的 std::visit 或构造调用根本无法通过 SFINAE。关键约束有两条:A 必须有 noexcept 析构函数,且至少支持移动构造/赋值(拷贝可选)。
最常踩的坑是带 raw pointer 成员、手动管理资源的类:
struct Bad { int* p; Bad() : p(new int{42}) {} ~Bad() { delete p; } // ❌ 不是 noexcept!默认不是 }; std::variant<Bad> v; // 编译失败:variant要求析构函数noexcept
- 修复方法:显式加
~Bad() noexcept = default;或手动实现并标记noexcept - 如果类不可移动(比如禁用了移动操作符),variant 无法在内部切换类型,编译时报错指向
std::variant内部的移动赋值操作 - 注意
std::monostate:它是零大小占位类型,常用来表示“空状态”,但别忘了它也参与类型列表长度计算
性能敏感场景下,variant比虚函数调用快,但比直接 if-else 慢
variant 的访问开销本质是:一次小型 switch(通常编译为跳转表或条件跳转),加上一次函数调用。它比虚函数调用略快(少一次 vtable 查找+间接跳转),但比手写的 if (holds_alternative<int>(v)) { ... } else if (...) { ... }</int> 多一次分支预测失败风险。
真正影响性能的不是 std::visit 本身,而是 visitor 里做的事儿。比如在热循环中反复访问 variant 并做字符串拼接,瓶颈就完全不在 variant 上。
- 如果类型数 ≤ 3 且分支逻辑极简,手写
std::get_if+ if 可能更高效(编译器更容易内联) - variant 的内存布局是“最大类型尺寸 + 1 字节 tag”,所以往里面塞大对象(如
std::Array<char></char>)会显著增加栈占用 - 跨 shared library 边界传递 variant 很危险——各模块对类型布局的理解可能不一致,尤其涉及模板实例化时
variant 不是银弹。它解决的是“有限、已知、异构类型集合”的安全访问问题,不是通用多态。把类型列表写长了、塞进不可控的第三方类、或者指望它替代接口抽象,都会在后期卡住。