c++ std::variant怎么用 c++类型安全的union【详解】

std::variant是C++17引入的类型安全联合体,可存储多种类型之一并运行时明确当前类型;它通过内部索引和类型标签确保访问安全,支持std::get、std::holds_alternative和std::visit等操作。

std::variant 是 C++17 引入的类型安全联合体(type-safe union),它能在一个对象中存储多种不同类型中的某一种,同时在运行时明确知道当前存的是哪一种——彻底避免了传统 union 的类型擦除和未定义行为风险。

核心特点:比 union 更安全、比 void* 更清晰

传统 union 不记录当前活跃成员,读写错误类型会触发未定义行为;std::variant 内部自带“当前索引”(index)和类型标签,所有访问都受检查:

  • 构造时自动记录所选类型
  • std::get(v)std::get(v) 访问前会做运行时校验(debug 模式下抛 std::bad_variant_access
  • 支持 std::holds_alternative(v) 判断是否持有某类型
  • 支持 std::visit 对不同情况统一处理,无需手动 switch index

基本用法:声明、构造、访问

假设你想表示一个可能为整数、浮点数或字符串的值:

#include 
#include 
#include 

using MyVariant = std::variant;

MyVariant v1 = 42; // 自动推导为 int MyVariant v2 = 3.14; // 推导为 double MyVariant v3 = std::string{"hi"}; // 必须显式构造 string,否则会被当 const char*

访问方式有三种常用形式:

  • std::get(v1) —— 按类型取,失败抛异常
  • std::get(v1) —— 按序号取(0=first type=int),同样带检查
  • std::holds_alternative(v1) —— 先判断再取,避免异常

推荐写法:用 std::visit 处理多态逻辑

比起一堆 if (holds_alternative<...>)std::visit 更简洁、更不易漏分支:

std::visit([](const auto& x) {
    using T = std::decay_t;
    if constexpr (std::is_same_v) {
        std::cout << "int: " << x << '\n';
    } else if constexpr (std::is_same_v) {
        std::cout << "double: " << x << '\n';
    } else if constexpr (std::is_same_v) {
        std::cout << "string: " << x << '\n';
    }
}, v1);

也可以用重载的 lambda(C++17 起支持):

auto printer = [](const auto& x) {
    std::cout << x << '\n';
};
std::visit(printer, v1); // 自动匹配对应类型的 operator()

实用细节与注意事项

  • std::monostate 可作为占位类型,让 variant 支持“空状态”,比如 std::variant<:monostate int std::string>
  • variant 不允许包含自身类型(如 std::variant>),但可通过 std::unique_ptr 间接实现递归结构
  • 移动语义完整支持:move 构造/赋值后原对象处于有效但未指定状态(通常为第一个类型的默认值)
  • 大小是各备选类型最大 size + 少量控制字节(一般 1–2 字节),比 std::any 更轻量