C++17 std::variant 实现类型安全的多态容器

在 C++17 之前,处理多种类型的数据往往需要使用 boost::variantstd::any 或者手写 std::vector<std::unique_ptr<Base>> 等方案。随着 std::variant 的加入,标准库为我们提供了一个编译期类型安全、无运行时开销的多态容器。下面通过一个完整示例,演示如何使用 std::variant 构造一个既灵活又安全的多态容器,并利用 std::visit 实现类型匹配与操作。

1. 变体(variant)的基本概念

std::variant<Ts...> 是一个联合体(union)的现代化实现,它可以存放几种指定类型中的任意一种,并且在编译期就知道可以存放哪些类型。它提供了以下关键特性:

特性 说明
类型安全 访问不到未存放的类型会抛出 std::bad_variant_access
无运行时开销 仅在编译期确定类型,内部实现使用联合体 + 整数标识
可组合 可以嵌套、与 std::optionalstd::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

发表评论