std::variant 是 C++17 引入的一个强类型联合体(type-safe union),它可以安全地在运行时存储多种不同类型的值。相比传统的 void* 或者自定义联合体,std::variant 提供了更好的类型安全、易用性和可维护性。下面我们将从基本概念、常用成员函数、访问方式以及与 std::visit 的配合使用几个方面来深入了解 std::variant。
1. 基本概念
#include <variant>
#include <iostream>
#include <string>
using namespace std;
int main() {
variant<int, string, double> v{42};
cout << v.index() << endl; // 输出 0,表示当前存储的是第一个类型(int)
cout << get<int>(v) << endl; // 输出 42
}
variant是一个模板类,接受任意数量的类型参数,表示它可以存储这些类型中的任意一种。index()返回当前存储类型在模板参数列表中的位置(从 0 开始)。如果未存储任何值,则返回variant_npos(定义在 ` ` 中)。- `get (v)` 用于获取当前存储的值,如果类型不匹配则抛出 `bad_variant_access`。
2. 常用成员函数
| 函数 | 作用 |
|---|---|
valueless_by_exception() |
检查是否因异常导致失效(当构造、赋值过程中抛出异常时会返回 true) |
valueless() |
与 valueless_by_exception() 等价 |
index() |
返回当前存储类型的索引 |
| `holds_alternative | |
| (v)` | 判断当前存储的类型是否为 T |
| `get | |
| (v)` | 取值,若类型不匹配则抛出异常 |
visit |
访问存储值的多态方式(见下文) |
| `emplace | |
| (args…)` | 在指定索引位置构造新值 |
swap(v1, v2) |
交换两个 variant 的值 |
3. 访问方式
3.1 传统访问
variant<int, string> v = "hello";
if (holds_alternative <string>(v))
cout << get<string>(v) << endl;
3.2 使用 std::visit
std::visit 结合一个可调用对象(如 lambda 或结构体)对存储的值进行访问,支持模式匹配式写法。
variant<int, string, double> v{3.14};
auto visitor = [](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>)
std::cout << "int: " << arg << '\n';
else if constexpr (std::is_same_v<T, double>)
std::cout << "double: " << arg << '\n';
else if constexpr (std::is_same_v<T, std::string>)
std::cout << "string: " << arg << '\n';
};
visit(visitor, v);
这种方式避免了显式检查类型,代码更简洁且类型安全。
4. 与 std::optional 的比较
std::optional只能存储单一类型,但允许“无值”状态。std::variant能存储多种类型,但不支持“无值”状态(除非使用std::monostate作为一种占位类型)。
如果你需要既有“无值”又有多种类型,可以组合使用:
variant<std::monostate, int, string> v{std::monostate{}};
5. 典型应用场景
5.1 结果与错误的统一返回
#include <variant>
#include <string>
using Result = std::variant<int, std::string>; // int 成功,string 为错误信息
Result do_something(int x) {
if (x >= 0) return x * 2;
return std::string("负数不可处理");
}
5.2 事件系统
struct ClickEvent { int x, y; };
struct KeyEvent { char key; };
struct ResizeEvent { int width, height; };
using Event = std::variant<ClickEvent, KeyEvent, ResizeEvent>;
void handle_event(const Event& e) {
std::visit(overloaded {
[](const ClickEvent& c){ /* 处理点击 */ },
[](const KeyEvent& k){ /* 处理键盘 */ },
[](const ResizeEvent& r){ /* 处理窗口大小改变 */ }
}, e);
}
6. 性能与实现细节
variant的大小等于其内部所有候选类型中最大类型的大小,再加上一个索引字段(通常是unsigned int或更小的位域)。因此,如果类型非常大,variant可能会占用较多内存。variant在构造、复制、移动时会根据索引调用相应类型的构造函数。异常安全保证:如果构造失败,variant将保持 valueless 状态。
7. 常见陷阱
- 忘记使用
std::monostate:如果需要表示“空”状态,记得加入std::monostate。 - **使用 `get ` 但不检查 `holds_alternative`**:这会导致运行时异常,最好使用 `visit` 或 `get_if`。
- 不清楚
valueless_by_exception的意义:如果构造失败,variant可能变成 valueless,后续访问会抛异常。
8. 小结
std::variant 为 C++ 提供了强类型联合体的实现,使得处理多种可能类型变得安全、清晰。通过 visit 的模式匹配式访问以及与 std::optional 的组合使用,能够满足各种复杂场景。掌握它后,你可以用更少的代码实现更安全、更易维护的逻辑。