std::variant 是 C++17 引入的一个类型安全的联合体,用来在运行时存储多种可能类型中的一种。它的核心理念是“和类型(sum type)”,与传统的 union 不同,std::variant 具有以下优势:
- 类型安全:编译器能够检查你访问的类型是否合法。
- 构造与析构自动管理:只会调用当前持有值的构造/析构函数。
- 不需要显式的标记:与传统
union需要手动维护类型标记不同,variant内部自动记录当前值的类型索引。
下面给出几个常见的使用场景,并配以示例代码,帮助你快速掌握 std::variant 的核心语法与技巧。
1. 基础语法
#include <variant>
#include <string>
#include <iostream>
int main() {
std::variant<int, double, std::string> v;
v = 42; // 赋值 int
std::cout << std::get<int>(v) << '\n';
v = 3.14; // 赋值 double
std::cout << std::get<double>(v) << '\n';
v = std::string("hello"); // 赋值 std::string
std::cout << std::get<std::string>(v) << '\n';
}
- `std::get (v)`:按类型获取值;若类型不匹配会抛出 `std::bad_variant_access`。
- `std::get_if (&v)`:返回指向值的指针;若类型不匹配返回 `nullptr`,因此不抛异常。
2. 获取当前索引
std::cout << "当前索引: " << v.index() << '\n'; // 0: int, 1: double, 2: std::string
index() 返回当前值所在的位置(从 0 开始)。如果你想把 variant 当作“标记枚举”使用,可以结合 index() 进行判断。
3. 访问多种类型(访问器)
std::visit 可以对 variant 进行模式匹配,像 switch 语句一样处理不同类型。
#include <variant>
#include <iostream>
int main() {
std::variant<int, double, std::string> v = "world";
std::visit([](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';
}
}, v);
}
std::visit的第一个参数是一个可调用对象(函数、lambda 等),第二个参数是variant。std::visit会自动调用对应类型的函数体,避免显式判断。
4. 递归 variant(Y-combinator 风格)
如果你需要在 variant 内部存储同一类型的递归结构(比如树形结构),可以利用 std::variant 的 recursive_wrapper:
#include <variant>
#include <vector>
#include <iostream>
struct Node;
using NodeVariant = std::variant<int, std::vector<std::variant<int, std::vector<NodeVariant>>>>;
struct Node {
NodeVariant data;
};
int main() {
Node root;
root.data = std::vector <NodeVariant>{ 1, 2, std::vector<NodeVariant>{3, 4} };
// 这里我们可以递归访问节点
}
5. 与 std::optional 配合使用
有时我们想要一个值要么存在(多种类型之一),要么不存在。可以将 std::variant 嵌套进 std::optional:
std::optional<std::variant<int, std::string>> optVar;
optVar = 10; // 有值,类型为 int
if (optVar) {
std::visit([](auto&& val){ std::cout << val << '\n'; }, *optVar);
}
6. 性能与注意事项
- 大小:
variant的大小为其最大成员的大小再加上足够的空间来存储索引(通常是std::size_t)。 - 拷贝/移动:若所有成员都满足
CopyAssignable或MoveAssignable,variant也会相应地实现。 - 异常安全:构造和析构时只会对当前持有的类型操作,避免了传统
union可能出现的未定义行为。 - 不可用
void:void不是合法成员类型;如果需要“空”占位,请使用std::monostate。
7. 小练习:实现一个简单的事件系统
#include <variant>
#include <iostream>
#include <vector>
struct MouseEvent { int x, y; };
struct KeyEvent { char key; };
using Event = std::variant<MouseEvent, KeyEvent, std::monostate>;
void dispatch(const Event& e) {
std::visit([](auto&& arg){
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, MouseEvent>) {
std::cout << "Mouse at (" << arg.x << ", " << arg.y << ")\n";
} else if constexpr (std::is_same_v<T, KeyEvent>) {
std::cout << "Key pressed: " << arg.key << '\n';
} else if constexpr (std::is_same_v<T, std::monostate>) {
std::cout << "No event\n";
}
}, e);
}
int main() {
std::vector <Event> events = { MouseEvent{10, 20}, KeyEvent{'A'}, std::monostate{} };
for (const auto& e : events) dispatch(e);
}
小结
std::variant是 C++17 标准库提供的类型安全多态工具,适合替代传统union和enum组合。std::get、std::get_if、index()以及std::visit是操作variant的核心 API。- 与
std::optional、递归类型、std::monostate等配合使用,可以实现更丰富的场景。
熟练掌握 std::variant 能让你在编写可维护、类型安全的代码时更加得心应手。祝编码愉快!