std::variant是C++17标准库提供的类型安全联合体,它可以存储指定类型列表中的某一个值,同一时间只能存储一种类型的值,且会自动处理存储对象的构造和析构,避免了传统union的类型安全隐患和内存管理问题。

std::variant基础声明与初始化
使用std::variant需要先包含<variant>头文件,声明时需要指定它支持的所有类型,初始化时可以赋值为其中任意一种类型的值。
#include <variant>
#include <string>
#include <iostream>
int main() {
// 声明一个支持int、double、std::string三种类型的variant
std::variant<int, double, std::string> var;
// 默认初始化为第一个类型的值,这里第一个类型是int,默认值为0
std::cout << std::get<int>(var) << std::endl; // 输出0
// 初始化时赋值为double类型
std::variant<int, double, std::string> var2 = 3.14;
// 初始化时赋值为字符串类型
std::variant<int, double, std::string> var3 = "hello";
return 0;
}
访问std::variant中的值
std::variant提供了多种访问内部值的方式,不同的方式适用不同的场景。
使用std::get访问
std::get可以通过类型或者索引获取variant中的值,如果当前存储的类型和指定的类型不匹配,会抛出std::bad_variant_access异常。
#include <variant>
#include <string>
#include <iostream>
int main() {
std::variant<int, double, std::string> var = "test";
try {
// 通过类型获取,当前存储的是string,获取int会抛异常
int val = std::get<int>(var);
std::cout << val << std::endl;
} catch (const std::bad_variant_access& e) {
std::cout << "获取int失败:" << e.what() << std::endl;
}
// 通过索引获取,索引从0开始,0对应int,1对应double,2对应string
std::string str_val = std::get<2>(var);
std::cout << "字符串值:" << str_val << std::endl;
return 0;
}
使用std::holds_alternative判断当前类型
在访问值之前,可以先使用std::holds_alternative判断variant当前存储的是否是指定类型,避免异常。
#include <variant>
#include <string>
#include <iostream>
int main() {
std::variant<int, double, std::string> var = 10;
if (std::holds_alternative<int>(var)) {
std::cout << "当前存储的是int类型,值为:" << std::get<int>(var) << std::endl;
} else if (std::holds_alternative<double>(var)) {
std::cout << "当前存储的是double类型" << std::endl;
} else {
std::cout << "当前存储的是string类型" << std::endl;
}
return 0;
}
使用std::visit访问
std::visit是更推荐的访问方式,它可以接收一个访问者对象和variant,自动根据当前存储的类型调用对应的处理逻辑,不需要手动判断类型,也不需要处理异常。
#include <variant>
#include <string>
#include <iostream>
// 定义访问者结构体,重载operator()处理不同类型
struct Visitor {
void operator()(int val) const {
std::cout << "处理int类型,值为:" << val << std::endl;
}
void operator()(double val) const {
std::cout << "处理double类型,值为:" << val << std::endl;
}
void operator()(const std::string& val) const {
std::cout << "处理string类型,值为:" << val << std::endl;
}
};
int main() {
std::variant<int, double, std::string> var1 = 5;
std::variant<int, double, std::string> var2 = 2.718;
std::variant<int, double, std::string> var3 = "variant test";
std::visit(Visitor{}, var1);
std::visit(Visitor{}, var2);
std::visit(Visitor{}, var3);
return 0;
}
修改std::variant的值
可以通过赋值的方式修改variant中存储的值,赋值的类型必须是variant声明时支持的类型之一,否则编译会报错。
#include <variant>
#include <string>
#include <iostream>
int main() {
std::variant<int, double, std::string> var = 10;
std::cout << "初始值:" << std::get<int>(var) << std::endl;
// 修改为double类型
var = 3.14;
std::cout << "修改后的值:" << std::get<double>(var) << std::endl;
// 修改为string类型
var = "new value";
std::cout << "修改后的值:" << std::get<std::string>(var) << std::endl;
return 0;
}
std::variant和传统union的对比
传统union存在诸多缺陷,std::variant在安全性和易用性上都有明显优势,具体差异如下:
| 对比项 | 传统union | std::variant |
|---|---|---|
| 类型安全 | 无类型安全,无法知道当前存储的类型,访问错误类型不会报错 | 类型安全,可通过接口判断当前类型,错误访问会抛异常或编译报错 |
| 对象生命周期管理 | 不会自动调用非平凡类型的构造和析构函数,需要手动管理 | 自动管理存储对象的构造和析构,支持非平凡类型 |
| 支持的类型 | 仅支持平凡类型(POD类型) | 支持任意可拷贝、可移动的类型,包括std::string、自定义类等 |
| 访问方式 | 需要通过成员访问,无类型校验 | 提供std::get、std::visit等多种安全访问方式 |
使用注意事项
- std::variant支持的类型列表中不能有重复类型,否则编译会报错。
- 如果variant的第一个类型是默认构造的,那么variant本身也可以默认构造,否则需要显式初始化。
- 使用std::get访问时,如果不确定当前类型,建议先配合std::holds_alternative判断,或者使用std::visit访问,避免异常。
- std::variant的大小是所有支持类型中最大的那个类型的大小加上类型索引的存储空间,会比单一类型占用更多内存。
std::variantc++类型安全联合体std::visitstd::holds_alternativestd::get修改时间:2026-06-16 06:39:21