std::variant 是 C++17 标准库中引入的一个类型安全的联合体(union)。它允许你在同一个变量中保存多种不同类型的值,并在运行时安全地访问当前存储的类型。下面我们从基础语法、常见用法、以及性能优化三大方面,系统地讲解如何在项目中高效使用 std::variant。
1. 基础语法与初始化
#include <variant>
#include <string>
#include <iostream>
using Var = std::variant<int, double, std::string>;
int main() {
Var v = 42; // 自动推断为 int
v = 3.14; // 切换为 double
v = std::string("hello"); // 切换为 std::string
// 访问当前类型
std::cout << std::get<int>(v) << '\n'; // 仅在 v 为 int 时有效
}
- 初始化:可以直接用对应类型的值或
std::variant构造函数。 - 类型访问:
- `std::get (v)`:如果 `v` 当前存储的是 `T`,返回对应引用,否则抛 `std::bad_variant_access`。
- `std::get_if (&v)`:返回指向当前类型的指针,若不匹配返回 `nullptr`。
std::visit:最常用的访问方式,使用访客(Visitor)对每种类型做统一处理。
2. 典型使用场景
2.1 解析 JSON 或其他动态数据
using JsonVal = std::variant<std::nullptr_t, bool, int, double, std::string,
std::vector <JsonVal>, std::map<std::string, JsonVal>>;
void print(const JsonVal& v) {
std::visit([](auto&& arg){
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, std::nullptr_t>) std::cout << "null";
else if constexpr (std::is_same_v<T, bool>) std::cout << (arg ? "true" : "false");
else if constexpr (std::is_same_v<T, int>) std::cout << arg;
else if constexpr (std::is_same_v<T, double>) std::cout << arg;
else if constexpr (std::is_same_v<T, std::string>) std::cout << '"' << arg << '"';
// 递归处理数组和对象
}, v);
}
2.2 命令行参数解析
using Arg = std::variant<std::string, int, bool>;
struct Option {
std::string name;
Arg value;
};
std::vector <Option> parseArgs(int argc, char** argv) {
// 简单示例,实际可使用 getopt 或 Boost.Program_options
}
2.3 事件系统
using Event = std::variant<MouseEvent, KeyEvent, ResizeEvent>;
void handleEvent(const Event& e) {
std::visit([](auto&& ev){ ev.handle(); }, e);
}
3. 性能与优化
-
避免不必要的拷贝
std::variant通过内部的std::aligned_union存储数据,使用值语义。若存储的类型较大或不具备移动语义,最好使用std::variant<std::shared_ptr<T>, …>或std::variant<std::unique_ptr<T>, …>。 -
访客模式最佳实践
- 采用
struct访客:可以拥有状态或缓存,避免多次重复计算。 - 若只需要返回值,使用
std::variant的std::visit并配合std::function,但要注意捕获列表的大小。
- 采用
-
**使用 `std::holds_alternative
`** 在不需要访问值,只想判断当前类型时,`std::holds_alternative` 的实现比 `std::get_if` 更快,因为它不需要生成指针。 -
对齐与内存占用
std::variant的大小等于其最大成员类型的大小(加上一字节用于标识类型),并按最大类型对齐。若成员类型差异很大,可能导致内存占用不均衡,可考虑使用std::optional+ 单独类型标识。
4. 与 std::optional 的区别与互补
std::variant<T1, T2, …> |
std::optional<T> |
|
|---|---|---|
| 作用 | 统一表示多种可能类型 | 统一表示可选值 |
| 内存占用 | max(sizeof(Ti)) + 1 |
sizeof(T) + 1 |
| 用法示例 | 解析 JSON 的节点类型 | 函数返回值是否有效 |
| 访问方式 | std::visit 或 std::get |
has_value() / value() |
在设计数据结构时,如果一个字段既可能为空又可能是多种类型,建议使用 std::variant;如果仅有“存在/不存在”之分,使用 std::optional 更合适。
5. 实战案例:实现一个简单的表达式求值器
#include <variant>
#include <string>
#include <unordered_map>
#include <stdexcept>
struct Value; // 前向声明
using Operand = std::variant<int, double, std::string, Value>;
struct Value {
// 简化:只支持整数和浮点数
std::variant<int, double> v;
double asDouble() const {
if (std::holds_alternative <int>(v))
return std::get <int>(v);
return std::get <double>(v);
}
};
struct BinaryOp {
std::string op; // "+", "-", "*", "/"
Operand left;
Operand right;
};
Value eval(const Operand&);
Value eval(const BinaryOp& bin) {
Value l = eval(bin.left);
Value r = eval(bin.right);
double lv = l.asDouble(), rv = r.asDouble();
if (bin.op == "+") return Value{lv + rv};
if (bin.op == "-") return Value{lv - rv};
if (bin.op == "*") return Value{lv * rv};
if (bin.op == "/") return Value{lv / rv};
throw std::runtime_error("未知运算符");
}
Value eval(const Operand& op) {
return std::visit([](auto&& arg){
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Value>) return arg;
else if constexpr (std::is_same_v<T, BinaryOp>) return eval(arg);
else if constexpr (std::is_same_v<T, int>) return Value{static_cast<double>(arg)};
else if constexpr (std::is_same_v<T, double>) return Value{arg};
else if constexpr (std::is_same_v<T, std::string>) {
// 这里省略变量解析,直接返回错误
throw std::runtime_error("未处理字符串");
}
}, op);
}
此示例展示了如何使用 std::variant 构建一个能处理多种操作数类型的表达式求值器,并通过 std::visit 统一处理不同类型。
6. 结语
std::variant 为 C++ 提供了类型安全的联合体,避免了传统 union 的潜在错误,并配合 std::visit 实现了功能强大的多态访问。掌握其基本语法、常见模式以及性能注意点,能够让你在解析动态数据、实现事件系统或构建脚本语言等场景中写出更健壮、更易维护的代码。下一步,尝试在自己的项目中引入 std::variant,并结合 std::optional 与 std::expected(C++23)实现完整的错误处理与多态返回值体系吧!