Files
workspace/cpp/JsonParse/std-variant.md
e2hang ebcee63b7c New
2026-01-09 00:05:37 +08:00

113 lines
4.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
`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. 基本用法
#### 定义和赋值
```cpp
#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`)
你需要明确告诉编译器你想取什么类型的值。
```cpp
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`)
为了避免抛出异常,通常会先检查或使用指针方式获取。
```cpp
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` 来根据当前存储的类型自动调用对应的处理逻辑。
```cpp
#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. 为什么要用它?(应用场景)
1. **解析异构数据**:比如解析 JSON 或配置文件,一个字段可能是数字,也可能是字符串。
2. **错误处理 (Result Type)**
* 函数可能返回“成功的结果”或者“错误码”。
* `std::variant<ResultData, ErrorCode>` 是非常优雅的实现方式。
3. **状态机**
* 如果一个对象有多个状态,且每个状态包含的数据不同。
* `std::variant<StateA, StateB, StateC>` 可以完美表示当前状态。
4. **替代虚函数多态**
* 如果你有一组已知类型,使用 `std::variant` + `std::visit` 通常比继承 + 虚函数更快(没有虚表开销,内存更紧凑)。
### 5. 总结
* **是什么**C++17 的类型安全联合体。
* **能存啥**:定义时指定的一组类型中的某一个。
* **内存**:在栈上分配(通常大小等于最大成员的大小 + 一个索引标记),没有额外的堆内存分配(除非成员本身分配堆内存,如 `std::string`)。
* **优点**:类型安全、性能高、无需手动管理生命周期。