std::visit 是访问 std::variant 值的强制要求,因 std::get 需编译期确定类型,否则抛 std::bad_variant_access 导致崩溃;正确做法是用 std::visit 统一分发并由编译器生成各类型的处理分支。

std::visit 是访问 std::variant 中当前持有值的唯一标准方式,不是可选技巧,而是强制要求——不通过 std::visit(或其变体如 std::holds_alternative + std::get)就无法安全取出值。
为什么不能直接用 std::get 访问 variant?
因为 std::get 要求编译期确定 v 当前确实持有类型 T,否则抛出 std::bad_variant_access。而运行时类型是动态的,硬写 std::get 很容易崩溃。
常见错误现象:std::terminate 被调用,控制台只显示 “terminate called after throwing an instance of 'std::bad_variant_access'” —— 这说明你绕过了类型检查,直接强取了错误分支。
正确做法是:先用 std::visit 统一分发,让编译器为你生成所有可能类型的处理分支:
立即学习“C++免费学习笔记(深入)”;
std::variantv = 3.14; std::visit([](const auto& x) { using T = std::decay_t ; if constexpr (std::is_same_v ) { std::cout << "int: " << x << "\n"; } else if constexpr (std::is_same_v ) { std::cout << "string: " << x << "\n"; } else if constexpr (std::is_same_v ) { std::cout << "double: " << x << "\n"; } }, v);
如何写一个能复用的 visitor 类?
Lambda 写多了会重复,尤其当多个地方要对同一 std::variant 做类似 dispatch 时。这时应定义具名 visitor 结构体,显式继承 std::variant 所有可选项的重载 operator():
- 必须为 variant 中每种类型都提供一个
operator()重载,否则编译失败(这是安全性的体现) - 避免使用模板
operator()单一入口,除非配合if constexpr,否则可能漏处理某类型 - 如果某些类型处理逻辑相同,可以共用一个重载,比如
void operator()(const std::string&) const和void operator()(const std::vector都转成 size 输出&) const
示例:
struct Printer {
void operator()(int i) const { std::cout << "int=" << i; }
void operator()(const std::string& s) const { std::cout << "str=" << s; }
void operator()(double d) const { std::cout << "dbl=" << d; }
};
// 使用
std::variant v = "hello";
std::visit(Printer{}, v); // 输出 str=hello
std::visit 的异常安全性与性能影响
std::visit 本身不抛异常(除非你 visitor 里主动 throw),但它内部会做一次运行时分支判断(通常编译为查表或条件跳转),开销极小,远低于虚函数调用或 dynamic_cast —— 因为 variant 的 type index 是内建存储的,无需 RTTI 查找。
注意两个易忽略点:
- 如果 variant 是空状态(
std::monostate或 move 后未赋值),std::visit仍会尝试调用 visitor,但不会匹配任何重载,导致编译失败;所以建议 variant 至少包含std::monostate作为默认项,并为其提供operator() - visitor 对象若含状态(比如捕获外部变量的 lambda),需确认其生命周期覆盖 visit 调用;临时 lambda 没问题,但局部 struct 引用外部栈变量就危险
替代虚函数多态时的关键设计权衡
用 std::variant + std::visit 替代基类指针/引用 + 虚函数,本质是从“运行时开放扩展”转向“编译期封闭枚举”。这意味着:
- 新增类型需修改 variant 定义、所有 visitor 实现、所有 switch-like 分支 —— 不适合插件化或未知子类场景
- 没有对象生命周期管理开销(不用 new/delete,无虚表指针,缓存友好)
- 编译器更容易内联 visitor 中的每个分支,实际性能常优于虚调用
- 无法实现“向上转型”或“动态添加行为”,比如不能把
std::variant隐式转成std::variant
所以它不是万能多态替代品,而是当你明确知道所有可能类型、且追求零成本抽象时的精准工具。
最常被忽略的是:variant 的“类型集合”一旦固定,就锁死了整个访问契约。改一个类型,所有 visitor 都得同步更新——这既是约束,也是文档。









