在 C++17 标准中,std::variant 成为处理多种类型值的强大工具。它类似于 boost::variant,但完全标准化、无外部依赖。variant 的核心特性是“类型安全的联合”,在运行时保证仅能持有一种类型的值,同时编译时提供类型信息。下面我们通过实例来说明如何使用 std::variant,以及在实际项目中常见的几种使用场景。
1. 基础使用
#include <variant>
#include <string>
#include <iostream>
int main() {
std::variant<int, std::string> v;
v = 10; // 存储整数
std::cout << std::get<int>(v) << '\n';
v = std::string("Hello"); // 存储字符串
std::cout << std::get<std::string>(v) << '\n';
// 通过 visit 访问
std::visit([](auto&& arg){ std::cout << arg << '\n'; }, v);
}
- `std::get (v)` 只能在当前持有的类型是 `T` 时使用,否则抛出 `std::bad_variant_access`。
std::visit采用访问者模式,对当前持有的类型执行对应的函数。
2. 访问器与访问错误处理
struct Visitor {
void operator()(int i) const { std::cout << "int: " << i << '\n'; }
void operator()(const std::string& s) const { std::cout << "string: " << s << '\n'; }
void operator()(double d) const { std::cout << "double: " << d << '\n'; }
};
std::variant<int, std::string, double> v = 3.14;
std::visit(Visitor{}, v); // 输出 double: 3.14
如果你不确定 variant 当前持有的类型,可以先用 `std::holds_alternative
(v)` 检查,或者在访问前使用 `std::get_if(&v)` 获取指针。 ### 3. 典型场景一:表示函数返回值 很多 C++ 库需要返回多种可能的结果:成功值、错误码、异常信息等。`std::variant` 可以用来包裹这些返回值,而不必单独抛异常或返回错误码。 “`cpp using Result = std::variant; // monostate 表示无返回值 Result readFile(const std::string& path) { if (path == “valid”) return 42; // 正常返回整数 if (path == “bad”) return std::string(“bad file”); // 错误信息 return std::monostate{}; // 例如操作不返回任何结果 } “` ### 4. 典型场景二:事件系统 在事件驱动的系统中,不同事件携带不同类型的数据。variant 可以用来统一事件的数据字段,避免大量的 `void*` 或者多重继承。 “`cpp struct MouseEvent { int x, y; }; struct KeyEvent { char key; }; using EventData = std::variant; void dispatch(const EventData& data) { std::visit([](auto&& ev){ using T = std::decay_t; if constexpr (std::is_same_v) { std::cout ) { std::cout , std::map >; “` ### 6. 常见坑与建议 1. **避免拷贝和移动代价**:variant 存储的是最常用类型的最大字节数,若所有候选类型都很大,可能导致空间浪费。使用 `std::shared_ptr` 或者 `std::unique_ptr` 作为候选类型,或通过 `std::variant` 包装智能指针。 2. **递归 variant**:如 JSON 示例中,variant 的元素类型包含自身,必须用 `std::vector ` 与 `std::map` 先声明 `JsonValue`,再用 `using` 重新定义。 3. **访问顺序**:`std::visit` 需要访问者函数覆盖所有可能类型,否则会触发编译错误。使用 `auto&&` 并在内部 `if constexpr` 检查类型可避免手动写所有分支。 4. **与 std::any 的区别**:`std::any` 在运行时可以存任何类型,但不提供类型安全的访问;variant 则在编译时已知所有可能类型,访问更安全。 ### 7. 小结 std::variant 是 C++17 的重要补丁,提供了一种类型安全、无运行时开销(除去类型信息)且易于使用的“多态”容器。无论是错误处理、事件分发、配置解析还是通用返回值包装,variant 都能显著提升代码可读性和安全性。掌握其基本语法、访问方式与常见使用模式后,你会发现它在日常项目中的价值不可忽视。