在 C++17 之后,std::variant 为我们提供了一种类型安全的多态容器,允许在同一对象中存放多种不同类型的值,同时在运行时可以安全地获取和操作当前存放的值。相比传统的继承+虚函数机制,std::variant 更加轻量、无运行时多态开销,并且不需要 RTTI。
1. 基本使用
#include <variant>
#include <iostream>
#include <string>
using Variant = std::variant<int, double, std::string>;
int main() {
Variant 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. 访问器(visitor)
当你需要根据当前存放的类型做不同处理时,最推荐的方式是使用 std::visit:
Variant v = 42;
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 自动把当前值作为唯一参数传给访问器,访问器使用模板参数推断得到具体类型,从而做相应处理。
3. 常见错误和陷阱
| 错误 | 原因 | 解决方案 |
|---|---|---|
std::variant<int, int> |
同一类型重复 | 去除重复类型 |
访问未初始化的 variant |
默认构造为 monostate |
先赋值后访问,或检查 index() |
| 使用 `std::get | ||
(v)直接访问 | 若类型不匹配抛异常 | 采用get_if或std::visit` 处理 |
||
variant 复制/移动失效 |
复制/移动时包含引用类型 | 仅使用值类型,或使用 std::reference_wrapper |
4. 与继承多态的对比
| 特性 | std::variant |
传统继承+虚函数 |
|---|---|---|
| 运行时开销 | O(1) 访问,无虚表 | 虚表查找 |
| 类型安全 | 编译期保证 | 需要 RTTI、dynamic_cast |
| 可维护性 | 简洁,类型集固定 | 随类增多复杂 |
| 适用场景 | 只需要有限几种类型 | 需要真正的多态行为 |
5. 高级技巧
5.1 结合 std::optional
using OptionalVariant = std::variant<std::monostate, int, std::string>;
OptionalVariant opt;
opt = std::monostate(); // 空值
// 或 opt = 42;
使用 std::monostate 作为占位类型,可以模拟可选值。
5.2 递归 variant
如果想让 variant 支持自身类型(例如树结构),可使用 std::unique_ptr 包装:
struct Node;
using NodePtr = std::unique_ptr <Node>;
using Variant = std::variant<int, std::string, NodePtr>;
struct Node {
Variant data;
};
5.3 与 std::any 的区别
std::any允许任意类型,存取时需要知道类型,且存在不安全转换。std::variant预先声明类型集合,访问更安全且性能更好。
6. 小结
std::variant 为 C++ 提供了一种强大而简洁的多态容器解决方案。通过结合 std::visit,我们可以在不使用虚函数的情况下,针对不同类型做出不同逻辑处理。它的主要优势在于:
- 类型安全
- 低运行时成本
- 代码可读性强
在实际项目中,当你需要处理“有限类型集合”而非“无限类型继承层次”时,std::variant 是一个非常值得尝试的选择。