C++ 中 std::variant 的使用与实现

std::variant 是 C++17 标准中引入的联合体类型,能够在运行时安全地保存多种类型中的一种。与传统的 union 不同,std::variant 对类型安全、内存管理、拷贝构造/移动构造等做了完整封装,使得多态性在类型层面更为可控。

1. 基本语法

#include <variant>
#include <string>
#include <iostream>

using Variant = std::variant<int, double, std::string>;

int main() {
    Variant v = 42;          // 保存 int
    v = std::string("hello"); // 现在保存 std::string
    std::cout << std::get<std::string>(v) << '\n';
}
  • 初始化:可以直接用括号或赋值语句。
  • 访问:`std::get (v)` 访问存储的值,如果类型不匹配会抛出 `std::bad_variant_access`。

2. 访问方式

方式 说明
`std::get
` 直接访问指定类型
`std::get_if
| 返回指针,若不匹配返回nullptr`
std::visit 访问器函数,支持多态访问

std::visit 示例

#include <variant>
#include <iostream>
#include <string>

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'; }
};

int main() {
    std::variant<int, double, std::string> v = 3.14;
    std::visit(Visitor{}, v);
}

std::visit 将会调用与当前存储类型匹配的 operator(),实现类似多重重载的效果。

3. 关联索引

std::variant 还提供了内部索引,表明当前存储的类型在模板参数列表中的位置。

std::cout << v.index() << '\n';  // 输出 1(double 在位置 1)

可使用 std::variant_npos 判断是否为空。

4. 空 variant

std::variant 可以是空的(没有值),此时使用 std::monostate 或默认构造。访问空 variant 会抛异常。

std::variant<int, std::string> v;
try {
    std::get <int>(v);  // 抛异常
} catch (const std::bad_variant_access& e) {
    std::cout << "Variant is empty\n";
}

5. 典型应用场景

  1. 函数返回多种可能类型

    std::variant<int, std::string> parse(const std::string& s) {
        if (std::isdigit(s[0]))
            return std::stoi(s);
        else
            return s;
    }
  2. 实现简单的状态机
    每个状态用一个独立类型表示,状态切换通过 std::variant 存储。

  3. 事件系统
    不同事件类型用不同结构体表示,统一用 std::variant 传递。

6. 性能考虑

  • 内存占用std::variant 的大小等于其最大成员的大小加上索引所需空间(通常是 size_t)。
  • 对齐:内部对齐确保所有成员均可安全存储。
  • 构造/析构:只会调用当前类型的构造/析构,避免无用操作。

7. 与 std::any 的区别

特性 std::variant std::any
类型安全 只允许预先声明的类型 任意类型
访问 需要指定类型 需要使用 `any_cast
`
性能 更小更快 更大更慢
用途 类型已知但不确定 类型未知

8. 进阶:自定义访问器

如果想在访问时做类型映射或处理,可以通过 std::visit 搭配 lambda 表达式:

auto result = std::visit([](auto&& arg) {
    using T = std::decay_t<decltype(arg)>;
    if constexpr (std::is_same_v<T, int>) return arg * 2;
    else if constexpr (std::is_same_v<T, std::string>) return arg + "!";
    else return arg;
}, v);

这段代码对不同类型执行不同逻辑,返回值根据 lambda 结果自动推断。

9. 兼容 C++20/23 的改进

  • std::variant 在 C++20 引入了 std::variant_alternativestd::variant_alternative_t
  • C++23 对 std::visit 进行了一些性能优化,内部实现采用了更快的分支预测。

10. 小结

std::variant 是一种安全、灵活且高效的多类型容器,适用于需要在运行时选择不同类型但类型已知的场景。掌握其基本用法、访问方式以及与 std::visit 的配合,将大大提升 C++ 代码的可维护性与可读性。

发表评论