4.3 KiB
4.3 KiB
std::variant 是 C++17 标准库引入的一个非常有用的模板类,定义在 <variant> 头文件中。
用一句话概括:它是 C++ 中的“类型安全的联合体 (Type-safe Union)”。
1. 核心概念
你可以把 std::variant<A, B, C> 想象成一个盒子,这个盒子在任何时刻只能装着 A、B 或 C 类型中的某一个值。
- 对比
struct/pair:结构体同时持有所有成员(A 和 B 和 C)。 - 对比
union:传统的 C 语言union也可以只持有一个值,但它不知道当前存的是什么类型,容易出错(类型不安全)。 - 对比
std::variant:它知道自己当前存的是什么类型,并且会自动管理内存(比如调用析构函数),非常安全。
2. 基本用法
定义和赋值
#include <iostream>
#include <variant>
#include <string>
int main() {
// 定义一个 variant,它可以是 int, double, 或者 string
std::variant<int, double, std::string> v;
v = 10; // 现在 v 存的是 int
v = 3.14; // 现在 v 存的是 double
v = "Hello World"; // 现在 v 存的是 string
return 0;
}
获取值 (std::get)
你需要明确告诉编译器你想取什么类型的值。
v = 100;
// 正确:当前存的是 int
int i = std::get<int>(v);
// 或者通过索引获取(0代表第一个类型 int)
int i2 = std::get<0>(v);
try {
// 错误:当前存的是 int,但你试图取 string
std::string s = std::get<std::string>(v);
} catch (const std::bad_variant_access& e) {
std::cout << "类型取错了: " << e.what() << std::endl;
}
安全检查 (std::holds_alternative 和 std::get_if)
为了避免抛出异常,通常会先检查或使用指针方式获取。
v = 3.14;
// 方法1:先检查类型
if (std::holds_alternative<double>(v)) {
std::cout << "里面是 double: " << std::get<double>(v) << std::endl;
}
// 方法2:使用 get_if (推荐,类似于 dynamic_cast)
if (auto* p = std::get_if<double>(&v)) { // 传入地址
std::cout << "里面是 double: " << *p << std::endl;
} else {
std::cout << "里面不是 double" << std::endl;
}
3. 高级用法:std::visit
这是 std::variant 最强大的地方。如果你不想写一堆 if-else 来判断当前是什么类型,可以使用 std::visit 来根据当前存储的类型自动调用对应的处理逻辑。
#include <variant>
#include <iostream>
int main() {
std::variant<int, double, std::string> v = "C++";
// 使用 lambda 表达式处理所有可能的情况
std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>)
std::cout << "它是 int: " << arg << std::endl;
else if constexpr (std::is_same_v<T, double>)
std::cout << "它是 double: " << arg << std::endl;
else if constexpr (std::is_same_v<T, std::string>)
std::cout << "它是 string: " << arg << std::endl;
}, v);
}
注:在实际工程中,通常会结合 overloaded 模式(一种辅助结构体)来为每种类型写单独的 lambda 函数,代码会更清晰。
4. 为什么要用它?(应用场景)
- 解析异构数据:比如解析 JSON 或配置文件,一个字段可能是数字,也可能是字符串。
- 错误处理 (Result Type):
- 函数可能返回“成功的结果”或者“错误码”。
std::variant<ResultData, ErrorCode>是非常优雅的实现方式。
- 状态机:
- 如果一个对象有多个状态,且每个状态包含的数据不同。
std::variant<StateA, StateB, StateC>可以完美表示当前状态。
- 替代虚函数多态:
- 如果你有一组已知类型,使用
std::variant+std::visit通常比继承 + 虚函数更快(没有虚表开销,内存更紧凑)。
- 如果你有一组已知类型,使用
5. 总结
- 是什么:C++17 的类型安全联合体。
- 能存啥:定义时指定的一组类型中的某一个。
- 内存:在栈上分配(通常大小等于最大成员的大小 + 一个索引标记),没有额外的堆内存分配(除非成员本身分配堆内存,如
std::string)。 - 优点:类型安全、性能高、无需手动管理生命周期。