C++中的std::variant怎么用?C++17安全的union类型【现代C++】

std::variant是C++17引入的类型安全可变容器,编译期确定类型列表,运行时仅持一个活跃值并自动管理生命周期;支持构造、std::get/holds_alternative安全访问及std::visit模式匹配处理。

std::variant 是 C++17 引入的类型安全的“可变类型容器”,可以看作是带类型检查、自动管理生命周期的现代 union。它不是运行时动态类型,而是在编译期确定所有可能类型,运行时只持有一个活跃值,并确保析构和访问都安全。

基本用法:定义、构造和访问

声明一个 variant,需明确列出所有允许的类型(最多 16 个,具体取决于实现):

示例:

std::variant v; // 初始状态为 int{0}(第一个类型的默认构造)

你可以用初始化列表、赋值或 std::make_variant_alternative(不常用)来构造:

  • v = 42; → 活跃类型变为 int
  • v = std::string{"hello"}; → 自动析构旧值,构造新 string
  • v = 3.14; → 活跃类型变为 double

安全获取值:std::get 和 std::holds_alternative

直接用 std::get(v) 获取指定类型的值,但若当前不持有该类型,会抛出 std::bad_variant_access 异常。

更推荐先判断再取值:

  • if (std::holds_alternative<:string>(v)) { ... }
  • auto& s = std::get<:string>(v); // 此时已确保安全

也可用 std::get_if(&v) 返回指针,空指针表示不匹配,避免异常开销。

统一处理多种类型:std::visit

这是 variant 最强大的用法——类似“模式匹配”,对当前活跃值执行对应逻辑,无需手动 if-else 判断类型。

写法一:lambda(推荐)

std::visit([](const auto& x) {
  if constexpr (std::is_same_v<:decay_t>, int>) {
    std::cout   } else if constexpr (std::is_same_v<:decay_t>, std::string>) {
    std::cout   }
}, v);

写法二:自定义 visitor 结构体(适合复用)

struct Printer {
  void operator()(int i) const { std::cout   void operator()(const std::string& s) const { std::cout   void operator()(double d) const { std::cout };

std::visit(Printer{}, v);

注意事项与常见坑

  • variant 不支持 void 或引用类型作为备选项(但可存 std::reference_wrapper
  • 所有备选项必须满足可析构、可移动(多数情况也要求可复制),否则编译失败
  • 默认构造只对第一个类型生效;若首类型不可默认构造,需显式初始化
  • 使用 std::monostate 可表示“空状态”(如 std::variant<:monostate int std::string>

基本上就这些。std::variant 不复杂,但容易忽略类型安全和访问方式的选择。用好它,能显著减少手工管理 union 的错误,让多态数据结构更清晰、更健壮。