在 C++17 标准中引入了 std::variant,它为处理多类型值提供了一种类型安全的方式。相比于传统的 union 或 std::any,std::variant 通过编译期类型检查避免了大部分运行时错误。本文将系统梳理 std::variant 的核心特性、常见使用场景以及一些实用技巧,帮助你在项目中高效利用这一工具。
一、基本语法与构造
#include <variant>
#include <iostream>
#include <string>
int main() {
std::variant<int, std::string> v1 = 42; // 初始化为 int
std::variant<int, std::string> v2 = "Hello"; // 初始化为 std::string
std::cout << std::get<int>(v1) << '\n';
std::cout << std::get<std::string>(v2) << '\n';
}
std::variant<Ts...>:模板参数包可以是任何拷贝构造和拷贝赋值可用的类型。- `std::get (v)`:获取存储值的类型为 `T` 时的引用;若类型不匹配抛出 `std::bad_variant_access`。
- `std::get_if (&v)`:返回指向值的指针,若不匹配则返回 `nullptr`。
二、访问值的更安全方式
1. std::visit
std::visit 允许你对不同类型执行不同逻辑,而无需显式判断类型。
auto visitor = [](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, std::string>) {
std::cout << "string: " << arg << '\n';
}
};
std::variant<int, std::string> v = "World";
std::visit(visitor, v);
2. std::holds_alternative
在需要提前判断类型时,可使用 `std::holds_alternative
(v)`: “`cpp if (std::holds_alternative (v)) { std::cout (v) ; Result r = std::monostate{}; // 表示空 “` #### 2. 使用 `std::variant` 代替 `std::any` – **类型安全**:`std::variant` 在编译期已知可存储的类型,`std::any` 只在运行时检查。 – **性能**:`std::variant` 采用平铺布局,避免了 `std::any` 的 heap 分配。 #### 3. 组合多个 `std::variant` 如果你需要一个“多值”容器,最简单的做法是嵌套: “`cpp using Pair = std::variant; using PairVec = std::vector ; “` 但在大规模数据时,考虑使用 `std::variant` 与 `std::tuple` 或 `std::any` 的混合方式,取决于业务需求。 — ### 四、常见错误与调试技巧 1. **忘记提供所有类型的拷贝构造** `std::variant` 要求所有类型都满足 `CopyConstructible`。如果你自定义类型,请显式实现拷贝构造。 2. **使用 `std::get` 时类型不匹配** 使用 `std::visit` 或 `std::holds_alternative` 可以避免运行时异常。 3. **性能瓶颈** `std::visit` 对每个访问都需要做类型判断。若访问频繁,考虑在外部缓存 `std::variant` 的索引: “`cpp int idx = v.index(); // 返回当前活跃类型的索引 “` — ### 五、实际案例:实现多态的轻量级错误处理 “`cpp enum class ErrorCode { None, NotFound, Invalid, Timeout }; struct Error { ErrorCode code; std::string message; }; using Result = std::variant; Result divide(int a, int b) { if (b == 0) { return Error{ErrorCode::Invalid, “division by zero”}; } return a / b; } int main() { Result r = divide(10, 0); std::visit([](auto&& val) { using T = std::decay_t; if constexpr (std::is_same_v) { std::cout ) { std::cout (val.code)