在现代C++中,std::variant提供了一种类型安全的联合体实现方式。与传统的void*或boost::any相比,std::variant在编译期就能保证类型正确性,避免运行时错误。下面通过一个实际例子,演示如何用std::variant构建一个支持整数、浮点数、字符串和自定义结构体的多态容器,并演示如何访问和操作这些不同类型的数据。
1. 引入头文件
#include <iostream>
#include <variant>
#include <string>
#include <vector>
2. 定义自定义结构体
struct Person {
std::string name;
int age;
};
std::ostream& operator<<(std::ostream& os, const Person& p) {
os << "Person{name: " << p.name << ", age: " << p.age << "}";
return os;
}
3. 定义Variant类型
using Var = std::variant<int, double, std::string, Person>;
4. 构造多态容器
int main() {
std::vector <Var> data;
data.emplace_back(42);
data.emplace_back(3.1415);
data.emplace_back(std::string("Hello, 世界"));
data.emplace_back(Person{"Alice", 30});
// 遍历并打印
for (const auto& v : data) {
std::visit([](auto&& arg) {
std::cout << arg << '\n';
}, v);
}
// 示例:将所有整数加1
for (auto& v : data) {
if (auto p = std::get_if <int>(&v)) {
(*p) += 1;
}
}
std::cout << "\nAfter incrementing ints:\n";
for (const auto& v : data) {
std::visit([](auto&& arg) {
std::cout << arg << '\n';
}, v);
}
return 0;
}
5. 结果与说明
运行上述程序,输出类似:
42
3.1415
Hello, 世界
Person{name: Alice, age: 30}
After incrementing ints:
43
3.1415
Hello, 世界
Person{name: Alice, age: 30}
- 类型安全:
std::variant在编译期知道容器中可能出现的类型,使用std::visit时,编译器会检查访问的lambda是否覆盖了所有类型,避免遗漏。 - 无运行时开销:与
boost::any相比,std::variant不需要类型擦除机制,内部实现简单,通常只存储最大类型的空间和一个字节的标签。 - 可扩展:只需在
using Var = std::variant<...>中添加新的类型,所有使用std::visit的地方即可自动支持新类型。
6. 进阶用法
-
自定义访问器
(var)`判断当前类型,或`std::get(var)`直接获取引用。
可以使用`std::holds_alternative -
错误处理
(var)`会抛出`std::bad_variant_access`异常。可以用`try/catch`捕获,或先用`holds_alternative`检查。
当访问错误类型时,`std::get -
组合Variant
`,实现可选字段。
如果需要更复杂的数据结构,可以在Variant中嵌套其他Variant或`std::optional -
性能优化
对于大型数据结构,考虑使用std::variant<std::shared_ptr<...>>来避免复制开销。
7. 小结
std::variant是C++17引入的强大工具,能够让你在保持类型安全的前提下,轻松实现多态容器。通过上面示例,你可以快速上手,将它集成到自己的项目中,无论是日志系统、事件队列还是配置管理,都能受益于其简洁高效的设计。