在 C++17 之前,处理多种类型的数据往往需要使用 boost::variant、std::any 或者手写 std::vector<std::unique_ptr<Base>> 等方案。随着 std::variant 的加入,标准库为我们提供了一个编译期类型安全、无运行时开销的多态容器。下面通过一个完整示例,演示如何使用 std::variant 构造一个既灵活又安全的多态容器,并利用 std::visit 实现类型匹配与操作。
1. 变体(variant)的基本概念
std::variant<Ts...> 是一个联合体(union)的现代化实现,它可以存放几种指定类型中的任意一种,并且在编译期就知道可以存放哪些类型。它提供了以下关键特性:
| 特性 | 说明 |
|---|---|
| 类型安全 | 访问不到未存放的类型会抛出 std::bad_variant_access |
| 无运行时开销 | 仅在编译期确定类型,内部实现使用联合体 + 整数标识 |
| 可组合 | 可以嵌套、与 std::optional、std::vector 等容器一起使用 |
2. 设计一个多态数据容器
假设我们需要处理一种日志条目,日志可以是字符串、整数或自定义结构 ErrorInfo。我们定义:
#include <variant>
#include <string>
#include <vector>
#include <iostream>
#include <iomanip>
#include <sstream>
struct ErrorInfo {
int code;
std::string message;
};
然后创建一个日志条目类型:
using LogEntry = std::variant<std::string, int, ErrorInfo>;
我们再把日志条目放入一个向量中,形成日志列表:
using LogBuffer = std::vector <LogEntry>;
3. 操作日志条目
3.1 访问并打印日志
使用 std::visit 可以对不同类型做不同处理。下面给出一个通用打印器:
struct LogPrinter {
void operator()(const std::string& s) const {
std::cout << "String: " << s << '\n';
}
void operator()(int n) const {
std::cout << "Integer: " << n << '\n';
}
void operator()(const ErrorInfo& e) const {
std::cout << "Error (" << e.code << "): " << e.message << '\n';
}
};
遍历日志缓冲区:
void printAll(const LogBuffer& buffer) {
for (const auto& entry : buffer) {
std::visit(LogPrinter{}, entry);
}
}
3.2 条件筛选日志
有时我们想只输出错误日志,可以通过 `std::holds_alternative
` 判断: “`cpp void printErrors(const LogBuffer& buffer) { for (const auto& entry : buffer) { if (std::holds_alternative (entry)) { std::visit(LogPrinter{}, entry); } } } “` ### 3.3 变体到字符串的通用转换 可以用 `std::visit` 与 `std::ostringstream` 组合,得到统一的字符串表示: “`cpp std::string entryToString(const LogEntry& entry) { std::ostringstream oss; std::visit([&oss](auto&& arg) { using T = std::decay_t; if constexpr (std::is_same_v) { oss ) { oss ) { oss