C++ 17 std::variant

C++ 17 std::variant

std::variantC++17 提供的一个类型安全联合体(type-safe union)。它可以在多个类型中保存其中一个值,但和传统 union 不同的是,它知道自己当前存的是哪一种类型。

1. 为什么需要 std::variant?

以前如果一个变量可能是多种类型,常见做法一般有几种:

  • union
  • 用基类指针 + 多态
  • 自己维护一个“类型标签 + 数据”的结构

这些方法都能解决问题,但要么不够安全,要么写起来比较重。
std::variant 可以把“多种可能类型中的一种”这个需求直接表达出来。

2. 基本使用

2.1 定义 variant

1
2
3
4
5
6
7
8
9
10
11
#include <variant>
#include <iostream>
using namespace std;

int main()
{
std::variant<int, double, string> data;
data = 10;
cout << get<int>(data) << endl;
return 0;
}

这里 data 可以在 intdoublestring 三种类型之间切换,但同一时刻只保存一种。

3. 访问值的方法

3.1 std::get

1
2
std::variant<int, string> v = 42;
cout << std::get<int>(v) << endl;

如果当前存的不是这个类型,std::get 会抛异常。

3.2 std::get_if

更稳妥一点的做法是:

1
2
3
if (auto p = std::get_if<int>(&v)) {
cout << *p << endl;
}

如果类型不匹配,它会返回 nullptr

3.3 index()

可以查看当前保存的是第几个候选类型:

1
cout << v.index() << endl;

4. std::visit

visitstd::variant 很重要的配套工具,用来根据当前实际类型执行不同逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <variant>
#include <iostream>
using namespace std;

int main()
{
std::variant<int, double, string> value = "hello";

std::visit([](const auto& arg) {
cout << arg << endl;
}, value);

return 0;
}

这样就不用手动一层层判断当前类型了。

5. 一个实际一点的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <variant>
#include <string>
#include <iostream>
using namespace std;

using ConfigValue = variant<int, bool, string>;

int main()
{
ConfigValue value = true;

visit([](const auto& arg) {
cout << arg << endl;
}, value);

return 0;
}

像这种“配置值可能是多种类型”的场景,variant 就很合适。

6. 和 union 的区别

union

  • 更底层
  • 需要手动管理当前类型
  • 对复杂类型支持不自然

variant

  • 类型安全
  • 知道当前保存的是哪种类型
  • 可以直接存放 std::string 这类复杂对象

7. 使用时要注意的点

7.1 std::get 可能抛异常

如果你不能确定当前类型,优先用 get_ifvisit

7.2 它不是“同时保存多个值”

variant<int, double> 不是同时有 intdouble,而是当前只会有一种。

7.3 第一类型可能作为默认构造结果

很多时候默认构造的 variant 会持有第一个可默认构造的类型,这一点要心里有数。

总结

std::variant 很适合表达“一个值可能是几种类型中的一种”这类需求。
相比手写类型标签或传统 union,它更安全,也更现代。对于配置系统、表达式树、消息载荷、状态值这类场景,还是很值得掌握的。