Files
workspace/cpp/JsonParse/std-variant.md
e2hang ebcee63b7c New
2026-01-09 00:05:37 +08:00

4.3 KiB
Raw Permalink Blame History

std::variant 是 C++17 标准库引入的一个非常有用的模板类,定义在 <variant> 头文件中。

用一句话概括:它是 C++ 中的“类型安全的联合体 (Type-safe Union)”。

1. 核心概念

你可以把 std::variant<A, B, C> 想象成一个盒子,这个盒子在任何时刻只能装着 A、B 或 C 类型中的某一个值。

  • 对比 struct/pair结构体同时持有所有成员A B C
  • 对比 union:传统的 C 语言 union 也可以只持有一个值,但它不知道当前存的是什么类型,容易出错(类型不安全)。
  • 对比 std::variant:它知道自己当前存的是什么类型,并且会自动管理内存(比如调用析构函数),非常安全。

2. 基本用法

定义和赋值

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

int main() {
    // 定义一个 variant它可以是 int, double, 或者 string
    std::variant<int, double, std::string> v;

    v = 10;             // 现在 v 存的是 int
    v = 3.14;           // 现在 v 存的是 double
    v = "Hello World";  // 现在 v 存的是 string

    return 0;
}

获取值 (std::get)

你需要明确告诉编译器你想取什么类型的值。

v = 100;

// 正确:当前存的是 int
int i = std::get<int>(v); 
// 或者通过索引获取0代表第一个类型 int
int i2 = std::get<0>(v);

try {
    // 错误:当前存的是 int但你试图取 string
    std::string s = std::get<std::string>(v); 
} catch (const std::bad_variant_access& e) {
    std::cout << "类型取错了: " << e.what() << std::endl;
}

安全检查 (std::holds_alternativestd::get_if)

为了避免抛出异常,通常会先检查或使用指针方式获取。

v = 3.14;

// 方法1先检查类型
if (std::holds_alternative<double>(v)) {
    std::cout << "里面是 double: " << std::get<double>(v) << std::endl;
}

// 方法2使用 get_if (推荐,类似于 dynamic_cast)
if (auto* p = std::get_if<double>(&v)) { // 传入地址
    std::cout << "里面是 double: " << *p << std::endl;
} else {
    std::cout << "里面不是 double" << std::endl;
}

3. 高级用法:std::visit

这是 std::variant 最强大的地方。如果你不想写一堆 if-else 来判断当前是什么类型,可以使用 std::visit 来根据当前存储的类型自动调用对应的处理逻辑。

#include <variant>
#include <iostream>

int main() {
    std::variant<int, double, std::string> v = "C++";

    // 使用 lambda 表达式处理所有可能的情况
    std::visit([](auto&& arg) {
        using T = std::decay_t<decltype(arg)>;
        if constexpr (std::is_same_v<T, int>)
            std::cout << "它是 int: " << arg << std::endl;
        else if constexpr (std::is_same_v<T, double>)
            std::cout << "它是 double: " << arg << std::endl;
        else if constexpr (std::is_same_v<T, std::string>)
            std::cout << "它是 string: " << arg << std::endl;
    }, v);
}

注:在实际工程中,通常会结合 overloaded 模式(一种辅助结构体)来为每种类型写单独的 lambda 函数,代码会更清晰。

4. 为什么要用它?(应用场景)

  1. 解析异构数据:比如解析 JSON 或配置文件,一个字段可能是数字,也可能是字符串。
  2. 错误处理 (Result Type)
    • 函数可能返回“成功的结果”或者“错误码”。
    • std::variant<ResultData, ErrorCode> 是非常优雅的实现方式。
  3. 状态机
    • 如果一个对象有多个状态,且每个状态包含的数据不同。
    • std::variant<StateA, StateB, StateC> 可以完美表示当前状态。
  4. 替代虚函数多态
    • 如果你有一组已知类型,使用 std::variant + std::visit 通常比继承 + 虚函数更快(没有虚表开销,内存更紧凑)。

5. 总结

  • 是什么C++17 的类型安全联合体。
  • 能存啥:定义时指定的一组类型中的某一个。
  • 内存:在栈上分配(通常大小等于最大成员的大小 + 一个索引标记),没有额外的堆内存分配(除非成员本身分配堆内存,如 std::string)。
  • 优点:类型安全、性能高、无需手动管理生命周期。