在 C++17 之后,标准库提供了 std::variant,它是一种强类型的和式(sum type)容器,能够在同一个对象中存放多种不同类型中的一种,同时保证类型安全。相比传统的 union 或者使用 void* 的做法,std::variant 提供了更安全、易用、可读性更好的多态实现方式。
1. 基本概念
std::variant<Types...> 定义了一个可以持有 Types... 其中一种类型的对象。其内部维护了一个索引(index())来标识当前持有的类型,并通过 get<T>() 或者 std::get<T>() 提取值。
2. 示例代码
#include <variant>
#include <iostream>
#include <string>
#include <vector>
using Variant = std::variant<int, double, std::string>;
void print(const Variant& v) {
std::visit([](auto&& arg) {
std::cout << "值: " << arg << std::endl;
}, v);
}
int main() {
Variant v1 = 42; // int
Variant v2 = 3.14; // double
Variant v3 = std::string("hello"); // std::string
print(v1);
print(v2);
print(v3);
// 通过索引访问
if (v1.index() == 0) {
std::cout << "v1 是 int,值为:" << std::get<int>(v1) << std::endl;
}
// 访问时自动检查类型
try {
std::cout << std::get<double>(v1) << std::endl; // 抛出异常
} catch (const std::bad_variant_access& e) {
std::cout << "错误: " << e.what() << std::endl;
}
return 0;
}
3. 访问方式
| 方法 | 说明 |
|---|---|
| `std::get | |
(v)| 直接访问,如果T与当前类型不匹配会抛出std::bad_variant_access` |
|
| `std::get_if | |
(&v)| 返回指向当前值的指针,若类型不匹配则返回nullptr` |
|
std::visit(visitor, v) |
对当前类型执行访问器(可为 lambda、函数对象等) |
4. 常见应用场景
-
配置系统
读取配置文件时,某些参数可能是整数、浮点数或字符串。使用std::variant可以避免类型转换错误。 -
消息框架
在消息传递系统中,每条消息可以携带不同类型的 payload。std::variant让消息类型与 payload 一一对应,避免裸指针。 -
表达式树
计算器或编译器中,节点可以是数字、变量、运算符等。std::variant使得树节点的实现更简洁。
5. 性能与注意事项
std::variant的实现通常使用联合(union)加上额外的索引存储,开销与手写联合相近。- 对于大型对象,建议使用
std::shared_ptr或std::unique_ptr包装后再放入variant,避免复制成本。 - 在 C++20 中,
std::variant的index()变得 constexpr,允许在编译期获取当前类型索引。
6. 小结
std::variant 为 C++ 提供了一种类型安全、表达力强的多态手段。相比传统的类型擦除或基类指针,variant 让代码更易维护,错误更易捕获。掌握 std::variant 的使用,可以在现代 C++ 项目中处理多种类型的值时更加得心应手。