在 C++17 标准中,std::variant 是一种强类型的联合体,它可以安全地存放多种类型中的一种,并提供了许多方便的操作方式。下面将从定义、访问、访客模式、与 std::visit 的配合以及一些实用技巧四个方面详细阐述 std::variant 的使用。
1. std::variant 的基本定义
#include <variant>
#include <string>
#include <iostream>
using Variant = std::variant<int, double, std::string>;
int main() {
Variant v1 = 10; // 直接初始化为 int
Variant v2 = 3.14; // 直接初始化为 double
Variant v3 = std::string("Hello"); // 直接初始化为 string
std::cout << std::get<int>(v1) << '\n'; // 访问 int
}
- 默认构造:若所有类型都有默认构造函数,则
Variant()会构造第一个类型的默认值。若不存在默认构造,必须显式初始化。 - 移动/拷贝:
std::variant支持拷贝和移动语义,只要其内部类型支持即可。
2. 安全访问方式
- `std::get (v)`:若 `v` 当前持有类型 `T`,返回对应值,否则抛出 `std::bad_variant_access`。
- `std::get_if (&v)`:若 `v` 当前持有类型 `T`,返回指向该值的指针,否则返回 `nullptr`。
std::visit:结合访客(visitor)模式,对当前值做统一处理。
void print(const Variant& v) {
std::visit([](auto&& arg) {
std::cout << arg << '\n';
}, v);
}
3. 访客模式详解
3.1 基础访客
struct Visitor {
void operator()(int i) const { std::cout << "int: " << i << '\n'; }
void operator()(double d) const { std::cout << "double: " << d << '\n'; }
void operator()(const std::string& s) const { std::cout << "string: " << s << '\n'; }
};
void demo() {
Variant v = 42;
std::visit(Visitor{}, v);
}
3.2 带返回值的访客
auto sum = std::visit([](auto&& arg) -> int {
return static_cast <int>(arg);
}, Variant{123}); // 若 arg 不是整型,会导致类型错误
4. 常见使用场景
- 命令模式:存储不同类型的命令对象,例如
std::variant<MoveCommand, AttackCommand, HealCommand>。 - 错误处理:返回值既可能是成功结果,也可能是错误码。例如
std::variant<std::string, ErrorCode>。 - 数据流:在解码网络包时,根据协议字段动态决定存储类型。
5. 与 std::optional 的组合
有时我们想要“可选且多类型”,可以用 std::optional<std::variant<...>>。
using OptVariant = std::optional <Variant>;
OptVariant parseInput(const std::string& token) {
if (token == "none") return std::nullopt;
if (std::isdigit(token[0])) return Variant{std::stoi(token)};
// 其它判断...
}
6. 性能与内存
std::variant在内部通常使用最大类型大小的对齐内存(std::aligned_union或 C++20 的std::aligned_union_t)。因此,若类型大小差异很大,内存占用会相应增大。- 访问成本:使用
std::visit需要动态分发(通过表指针或虚拟函数表),在高性能场景需注意。
7. 小技巧
- 使用
std::visit与std::forward:若需要在访客中保持值的移动语义,可用 `std::forward (arg)`。 - 自定义错误信息:
std::visit可与std::apply结合,用来快速打印所有字段。 - 多继承:若
Variant的元素类型继承自同一基类,可考虑使用 `std::unique_ptr
8. 参考代码
完整示例:
#include <variant>
#include <string>
#include <iostream>
#include <optional>
using Variant = std::variant<int, double, std::string>;
using OptVariant = std::optional <Variant>;
struct PrintVisitor {
void operator()(int i) const { std::cout << "int: " << i << '\n'; }
void operator()(double d) const { std::cout << "double: " << d << '\n'; }
void operator()(const std::string& s) const { std::cout << "string: " << s << '\n'; }
};
OptVariant parse(const std::string& token) {
if (token.empty()) return std::nullopt;
if (std::all_of(token.begin(), token.end(), ::isdigit))
return Variant{std::stoi(token)};
try {
double d = std::stod(token);
return Variant{d};
} catch(...) {}
return Variant{token};
}
int main() {
OptVariant opt = parse("123");
if (opt) {
std::visit(PrintVisitor{}, *opt);
} else {
std::cout << "无效输入\n";
}
}
9. 小结
std::variant 为 C++ 提供了一种类型安全的多态实现手段。通过结合访客模式、std::visit 与 std::optional,我们可以构建既灵活又安全的数据结构。合理规划类型集合、关注内存占用与访问效率,即可在实际项目中充分发挥 std::variant 的优势。祝编码愉快!