在 C++20 之前,处理多种可能类型的数据往往要用 boost::variant 或自己实现类似的类型安全包装。随着标准库引入 std::variant,我们可以在编译期就确定多态类型集合,既保证了类型安全,又能在运行时轻松切换和访问。本文将从 std::variant 的基本使用、访问方式、错误处理以及调试技巧四个方面,详细阐述如何利用它实现高效、可维护的代码。
1. 基础语法与构造
#include <variant>
#include <string>
#include <iostream>
using Value = std::variant<int, double, std::string>;
int main() {
Value v1 = 42; // 整型
Value v2 = 3.14; // 双精度浮点
Value v3 = std::string("hello"); // 字符串
std::cout << v1 << '\n';
}
- 默认构造:如果
Value中没有std::monostate,必须显式初始化,否则编译错误。 - 类型列表:使用
std::variant<Ts...>传入一组可互斥的类型。 - 拷贝与移动:
variant支持拷贝构造、移动构造、赋值,满足 C++ 通用容器的行为。
2. 访问方式
2.1 std::get
try {
int i = std::get <int>(v1); // 若 v1 不是 int 则抛异常 std::bad_variant_access
} catch (const std::bad_variant_access&) {
std::cerr << "类型不匹配\n";
}
- 优点:直接按类型获取。
- 缺点:若类型不匹配抛异常,必须捕获或使用
std::holds_alternative先检查。
2.2 std::get_if
if (auto p = std::get_if <double>(&v2)) {
std::cout << "double: " << *p << '\n';
}
- 返回指针:若类型不匹配,返回
nullptr,无异常抛出。
2.3 std::visit
std::visit([](auto&& arg){
std::cout << arg << '\n';
}, v3);
- 通用访问:适用于多种类型的统一处理。
- 可传递自定义 visitor:如
struct Visitor { void operator()(int) {...} void operator()(double) {...} ... };
3. 典型场景举例
3.1 表达式求值树
struct Add {
std::variant<int, double> lhs, rhs;
};
struct Subtract { ... };
using Expr = std::variant<Add, Subtract, int, double>;
double eval(const Expr& e) {
return std::visit(overloaded{
[](int i) { return static_cast <double>(i); },
[](double d) { return d; },
[](const Add& a) { return eval(a.lhs) + eval(a.rhs); },
[](const Subtract& s) { return eval(s.lhs) - eval(s.rhs); }
}, e);
}
3.2 事件系统
struct KeyEvent { char key; };
struct MouseEvent { int x, y; };
using Event = std::variant<KeyEvent, MouseEvent>;
void handle(const Event& e) {
std::visit([](auto&& ev){
using T = std::decay_t<decltype(ev)>;
if constexpr (std::is_same_v<T, KeyEvent>) {
std::cout << "Key: " << ev.key << '\n';
} else if constexpr (std::is_same_v<T, MouseEvent>) {
std::cout << "Mouse: (" << ev.x << "," << ev.y << ")\n";
}
}, e);
}
4. 调试技巧
-
打印当前索引
std::cout << v.index() << '\n';// 0-based 索引,配合type()。 -
使用
std::visit打印所有类型std::visit([](auto&& arg){ std::cout << arg << '\n'; }, v); -
**借助 `std::variant_size_v
`** 预先验证访问索引合法性。 -
在断言中检查类型
(v));`
`assert(std::holds_alternative
5. 性能与注意事项
- 内存占用:
variant的大小为max(sizeof(Ts...)) + alignof(max(Ts...))。若类型差异较大,可考虑std::any。 - 构造成本:每次切换类型时需要复制/移动目标类型对象,避免频繁切换或使用 `std::optional ` 作为内部容器。
- 异常安全:
std::visit的 visitor 必须满足异常安全,若有可能抛异常,建议在 visitor 内部捕获。
6. 结语
std::variant 为 C++ 提供了强大的类型安全多态容器,既能避免传统 union 的不安全,也能取代第三方 boost::variant 的繁琐。通过正确使用 get, get_if, visit 等 API,我们可以编写既简洁又健壮的代码。熟悉 std::variant 的各种技巧,将大大提升 C++ 开发者在复杂类型交互场景下的效率和代码质量。