在 C++17 之后,std::variant 成为处理多类型值的一种强大工具。它是一个类型安全的联合(union),类似于 std::optional,但可以容纳多种类型。本文将从基本语法、常用成员函数、与传统 std::variant 的区别、以及实际应用场景进行详细阐述,帮助你在项目中高效使用 std::variant。
1. 基础语法
#include <variant>
#include <iostream>
#include <string>
using Result = std::variant<int, double, std::string>;
int main() {
Result r = 42; // 初始化为 int
std::cout << std::get<int>(r) << '\n';
r = std::string("Hello"); // 切换为 string
std::visit([](auto&& arg){ std::cout << arg << '\n'; }, r);
}
std::variant<Ts...>:接受一个或多个类型参数。- 只能存储这些类型之一。若存储不兼容类型,会在编译阶段报错。
- 默认构造函数会默认初始化第一个类型。
2. 主要成员函数
| 函数 | 说明 |
|---|---|
value() |
访问当前类型值;若类型不匹配则抛出 std::bad_variant_access。 |
value_or() |
若当前类型不是所请求的,返回默认值。 |
index() |
返回当前持有的类型索引(从 0 开始)。 |
| `holds_alternative | |
()| 判断是否持有T` 类型。 |
|
| `emplace | |
(args…)| 直接构造T` 并替换当前值。 |
|
operator= |
赋值或移动操作。 |
swap() |
与另一个 variant 交换内容。 |
小技巧:在访问时使用 `std::get_if
(&variant)` 可以获得指针,避免异常抛出。
3. 与 std::any 的区别
| 特点 | std::variant |
std::any |
|---|---|---|
| 类型安全 | 在编译期检查;只能是指定类型 | 运行时检查 |
| 性能 | 小且常量时间 | 动态分配 |
| 用途 | 用于已知有限类型集合 | 用于任意类型 |
当你需要处理一组已知类型的值时,优先使用
std::variant。
4. 与传统的联合(union)的比较
union MyUnion {
int i;
double d;
std::string s; // 不能使用非平凡类型
};
std::variant内部维护了类型信息,避免了错误的类型访问。- 支持构造函数、析构函数、赋值操作,满足 RAII 要求。
- 自动进行深拷贝、移动,降低错误率。
5. 典型使用场景
5.1 命令行参数解析
using Arg = std::variant<int, std::string, bool>;
std::map<std::string, Arg> config;
config["threads"] = 4;
config["verbose"] = true;
config["output"] = std::string("log.txt");
5.2 事件系统
struct MouseEvent { int x, y; };
struct KeyEvent { char key; };
using Event = std::variant<MouseEvent, KeyEvent>;
void handleEvent(const Event& e) {
std::visit([](auto&& event){
using T = std::decay_t<decltype(event)>;
if constexpr (std::is_same_v<T, MouseEvent>)
std::cout << "Mouse at (" << event.x << ',' << event.y << ")\n";
else if constexpr (std::is_same_v<T, KeyEvent>)
std::cout << "Key pressed: " << event.key << '\n';
}, e);
}
5.3 表达式求值树
struct Literal { double value; };
struct Add { std::shared_ptr <Expr> left, right; };
using Expr = std::variant<Literal, Add>;
double evaluate(const Expr& expr) {
return std::visit([](auto&& node){
using T = std::decay_t<decltype(node)>;
if constexpr (std::is_same_v<T, Literal>)
return node.value;
else if constexpr (std::is_same_v<T, Add>)
return evaluate(*node.left) + evaluate(*node.right);
}, expr);
}
6. 常见坑与优化
- 异常安全:`emplace ()` 会先析构旧值再构造新值,若构造抛异常会导致内部状态不一致。可以使用 `std::in_place_type_t` 进行更细粒度控制。
- 索引值:
index()的返回值从 0 开始,可能导致误解。最好使用 `holds_alternative ()`。 - 性能:对大量 variant 对象进行访问时,
std::visit的类型擦除会产生函数指针开销。可以使用std::visit的函数指针表或std::variant_alternative_t手动实现。 - 与结构体:若 variant 只包含
struct,请确保struct具有默认构造、复制、移动构造。
7. 小结
std::variant 是 C++17 之后处理多态值的首选工具。它提供了类型安全、性能友好、易于使用的接口,能够替代传统联合和 std::any。掌握其基本语法、成员函数以及常见的使用模式后,你可以在项目中大幅提升代码的可维护性和安全性。希望本文能帮助你快速上手并充分利用 std::variant 的优势。