C++ 中如何使用 std::variant 实现类型安全的多态容器?

在 C++17 之后,std::variant 为我们提供了一种类型安全的多态容器,允许在同一对象中存放多种不同类型的值,同时在运行时可以安全地获取和操作当前存放的值。相比传统的继承+虚函数机制,std::variant 更加轻量、无运行时多态开销,并且不需要 RTTI。

1. 基本使用

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

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

int main() {
    Variant v = 42;               // 存放 int
    std::cout << std::get<int>(v) << '\n';

    v = 3.14;                     // 重新赋值为 double
    std::cout << std::get<double>(v) << '\n';

    v = std::string("hello");     // 存放 std::string
    std::cout << std::get<std::string>(v) << '\n';
}
  • `std::get (v)` 直接取值,若类型不匹配会抛出 `std::bad_variant_access`。
  • `std::get_if (&v)` 取可选指针,匹配失败返回 `nullptr`。

2. 访问器(visitor)

当你需要根据当前存放的类型做不同处理时,最推荐的方式是使用 std::visit

Variant v = 42;

std::visit([](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, double>) {
        std::cout << "double: " << arg << '\n';
    } else if constexpr (std::is_same_v<T, std::string>) {
        std::cout << "string: " << arg << '\n';
    }
}, v);

std::visit 自动把当前值作为唯一参数传给访问器,访问器使用模板参数推断得到具体类型,从而做相应处理。

3. 常见错误和陷阱

错误 原因 解决方案
std::variant<int, int> 同一类型重复 去除重复类型
访问未初始化的 variant 默认构造为 monostate 先赋值后访问,或检查 index()
使用 `std::get
(v)直接访问 | 若类型不匹配抛异常 | 采用get_ifstd::visit` 处理
variant 复制/移动失效 复制/移动时包含引用类型 仅使用值类型,或使用 std::reference_wrapper

4. 与继承多态的对比

特性 std::variant 传统继承+虚函数
运行时开销 O(1) 访问,无虚表 虚表查找
类型安全 编译期保证 需要 RTTI、dynamic_cast
可维护性 简洁,类型集固定 随类增多复杂
适用场景 只需要有限几种类型 需要真正的多态行为

5. 高级技巧

5.1 结合 std::optional

using OptionalVariant = std::variant<std::monostate, int, std::string>;

OptionalVariant opt;
opt = std::monostate(); // 空值
// 或 opt = 42;

使用 std::monostate 作为占位类型,可以模拟可选值。

5.2 递归 variant

如果想让 variant 支持自身类型(例如树结构),可使用 std::unique_ptr 包装:

struct Node;
using NodePtr = std::unique_ptr <Node>;
using Variant = std::variant<int, std::string, NodePtr>;

struct Node {
    Variant data;
};

5.3 与 std::any 的区别

  • std::any 允许任意类型,存取时需要知道类型,且存在不安全转换。
  • std::variant 预先声明类型集合,访问更安全且性能更好。

6. 小结

std::variant 为 C++ 提供了一种强大而简洁的多态容器解决方案。通过结合 std::visit,我们可以在不使用虚函数的情况下,针对不同类型做出不同逻辑处理。它的主要优势在于:

  • 类型安全
  • 低运行时成本
  • 代码可读性强

在实际项目中,当你需要处理“有限类型集合”而非“无限类型继承层次”时,std::variant 是一个非常值得尝试的选择。

发表评论