std::any是C++17标准新增的类型,定义在<any>头文件中,它允许我们在运行时存储任意可拷贝构造的类型实例,并且可以在后续操作中检查存储的类型是否符合预期,相比传统的void*指针方案,它具备更好的类型安全性。

std::any的基本使用流程
使用std::any存储和读取数据主要分为三个步骤:存入数据、判断存储类型、取出数据。下面通过简单示例说明基础用法。
1. 存入数据
我们可以直接通过赋值或者构造函数将任意可拷贝类型的数据存入std::any对象中,支持内置类型、自定义类型、标准库容器等。
#include <any>
#include <iostream>
#include <string>
#include <vector>
// 自定义可拷贝类型
struct User {
std::string name;
int age;
};
int main() {
// 存储内置类型
std::any a1 = 10;
std::any a2 = 3.14;
std::any a3 = std::string("hello std::any");
// 存储自定义类型
std::any a4 = User{"张三", 25};
// 存储标准库容器
std::any a5 = std::vector<int>{1, 2, 3, 4};
return 0;
}
2. 判断存储的类型
在取出数据之前,我们需要先确认std::any对象中存储的类型是否符合预期,避免错误的类型转换导致异常。可以使用type()方法获取存储类型的type_info,再和目标的type_info比较。
#include <any>
#include <iostream>
#include <string>
#include <typeinfo>
int main() {
std::any a = 100;
// 判断存储的是否是int类型
if (a.type() == typeid(int)) {
std::cout << "存储的类型是int" << std::endl;
} else {
std::cout << "存储的类型不是int" << std::endl;
}
// 判断存储的是否是string类型
if (a.type() == typeid(std::string)) {
std::cout << "存储的类型是string" << std::endl;
}
return 0;
}
3. 取出数据
取出数据需要使用std::any_cast函数,它支持两种使用方式:一种是传入std::any对象的指针,转换失败返回空指针;另一种是传入std::any对象的引用,转换失败会抛出std::bad_any_cast异常。
#include <any>
#include <iostream>
#include <string>
int main() {
std::any a = std::string("test data");
// 方式1:指针形式,失败返回nullptr
std::string* ptr = std::any_cast<std::string>(&a);
if (ptr != nullptr) {
std::cout << "指针方式取出数据:" << *ptr << std::endl;
}
// 方式2:引用形式,失败抛出异常
try {
std::string val = std::any_cast<std::string>(a);
std::cout << "引用方式取出数据:" << val << std::endl;
} catch (const std::bad_any_cast& e) {
std::cout << "类型转换失败:" << e.what() << std::endl;
}
// 错误类型转换示例
try {
int wrong_val = std::any_cast<int>(a);
} catch (const std::bad_any_cast& e) {
std::cout << "错误转换捕获:" << e.what() << std::endl;
}
return 0;
}
std::any的常用操作
判断是否有值
可以使用has_value()方法判断std::any对象是否存储了有效数据,空对象调用该方法会返回false。
#include <any>
#include <iostream>
int main() {
std::any a1;
std::any a2 = 20;
std::cout << "a1是否有值:" << (a1.has_value() ? "是" : "否") << std::endl;
std::cout << "a2是否有值:" << (a2.has_value() ? "是" : "否") << std::endl;
return 0;
}
清空存储的数据
调用reset()方法可以清空std::any对象中存储的数据,清空后has_value()会返回false。
#include <any>
#include <iostream>
int main() {
std::any a = 100;
std::cout << "清空前是否有值:" << (a.has_value() ? "是" : "否") << std::endl;
a.reset();
std::cout << "清空后是否有值:" << (a.has_value() ? "是" : "否") << std::endl;
return 0;
}
替换存储的数据
可以直接对std::any对象赋值新的数据,会自动替换之前存储的内容,类型也可以和之前不同。
#include <any>
#include <iostream>
#include <string>
int main() {
std::any a = 10;
std::cout << "初始类型:" << a.type().name() << std::endl;
// 替换为string类型
a = std::string("new value");
std::cout << "替换后类型:" << a.type().name() << std::endl;
std::cout << "替换后的值:" << std::any_cast<std::string>(a) << std::endl;
return 0;
}
使用注意事项
- std::any只能存储可拷贝构造的类型,如果自定义类型没有拷贝构造函数,无法存入std::any。
- 取出数据时的类型必须和存储的类型完全匹配,比如存储的是
const char*,不能用std::string去转换,否则会抛出异常。 - 不要存储引用类型或者指针指向临时对象,避免std::any存储的对象失效导致未定义行为。
- std::any会带来一定的性能开销,因为它内部需要管理动态内存和类型信息,如果对性能要求极高的场景,需要谨慎使用。
实际应用场景
std::any适合用在需要临时存储不确定类型的场景,比如通用配置项存储、回调函数参数传递、异构数据容器等。下面是一个简单的通用配置存储示例:
#include <any>
#include <iostream>
#include <string>
#include <unordered_map>
// 通用配置容器,key是配置名,value是配置值
std::unordered_map<std::string, std::any> config_map;
// 设置配置
void set_config(const std::string& key, const std::any& value) {
config_map[key] = value;
}
// 获取配置
template <typename T>
T get_config(const std::string& key) {
auto it = config_map.find(key);
if (it == config_map.end()) {
throw std::runtime_error("配置不存在");
}
return std::any_cast<T>(it->second);
}
int main() {
// 设置不同类型的配置
set_config("max_count", 100);
set_config("app_name", std::string("test_app"));
set_config("enable_log", true);
// 读取配置
int max_count = get_config<int>("max_count");
std::string app_name = get_config<std::string>("app_name");
bool enable_log = get_config<bool>("enable_log");
std::cout << "max_count: " << max_count << std::endl;
std::cout << "app_name: " << app_name << std::endl;
std::cout << "enable_log: " << (enable_log ? "true" : "false") << std::endl;
return 0;
}