C++17 中std::any 详解和代码示例
1. 什么是 std::any
std::any
是 C++17 引入的一种类型安全的容器,可以存储任意类型的单个值。- 它类似于“类型擦除”的容器:存储时不关心具体类型,取出时需要显式指定类型。
- 与
void*
的区别:std::any
保留了类型信息,取出时能安全检查。
2. 定义与头文件
#include <any>std::any a; // 默认空
-
空状态:刚创建或
reset()
后any
为空 (has_value() == false
)。 -
std::any
可以存放:- 内置类型(int, double, …)
- 自定义类
- 智能指针
- 任何可拷贝构造的类型
3. 构造与赋值
std::any a = 10; // 存 int
a = 3.14; // 存 double
a = std::string("Hello"); // 存 std::string
- 拷贝构造:复制存储的对象
- 移动构造:转移存储的对象,避免拷贝
4. 主要接口
① 存储
std::any a(42); // 构造存储 int
a = std::string("Hi"); // 赋值新值
② 判断是否存储
if (a.has_value()) {std::cout << "Not empty\n";
}
③ 类型信息
std::cout << a.type().name() << std::endl;
⚠
type().name()
返回的名字依赖编译器,不保证可读。
④ 取值
必须用 std::any_cast<T>
:
std::any a = 123;
int x = std::any_cast<int>(a); // OK
取引用,避免拷贝:
std::string s = "hello";
std::any a = s;std::string& ref = std::any_cast<std::string&>(a); // 引用
ref[0] = 'H'; // 修改 a 内部的值
取指针,安全检查:
if (auto p = std::any_cast<int>(&a)) {std::cout << "Value = " << *p;
}
⑤ 清空
a.reset(); // 清空
5. 使用示例
#include <iostream>
#include <any>
#include <string>int main() {std::any a;a = 42;std::cout << std::any_cast<int>(a) << std::endl;a = std::string("Hello");std::cout << std::any_cast<std::string>(a) << std::endl;// 安全检查if (auto p = std::any_cast<std::string>(&a)) {std::cout << "string length = " << p->size() << std::endl;}
}
6. 实现原理(简要)
-
std::any
内部采用 类型擦除(type erasure) 技术:- 保存一个
void*
指针 + vtable(记录复制/析构函数)。 - 存储时构建对象并保存类型信息
typeid(T)
。 - 取出时
any_cast
检查类型是否匹配,不匹配则抛出std::bad_any_cast
异常。
- 保存一个
7. 常见场景
-
通用容器(存放不同类型的数据):
std::vector<std::any> vec; vec.push_back(1); vec.push_back(3.14); vec.push_back(std::string("hi"));
-
插件系统 / 事件系统:不同模块通过
std::any
传递任意类型数据。 -
配置参数存储:存储不同类型的配置项。
8. 常见坑点
-
取值类型必须完全匹配
std::any a = 1; std::cout << std::any_cast<long>(a); // 抛出异常
必须用
int
取。 -
性能不是最好
std::any
内部需要堆分配(除非 SSO 优化)。- 如果追求性能,可以考虑
variant
(固定类型集合)。
-
存储类型必须可拷贝
- 不能存储
unique_ptr
这种不可拷贝对象(C++20 后可通过make_any
支持移动构造)。
- 不能存储
9. 对比其他类型擦除机制
特性 | std::any | std::variant | void* |
---|---|---|---|
类型范围 | 任意类型 | 限定类型集合 | 任意类型 |
类型安全 | ✅ | ✅ | ❌ |
取值方式 | any_cast | std::get | 强制转换 |
运行时开销 | 较高 | 较低 | 较低 |
10.事件系统示例:std::any
消息总线
1. 基础实现
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
#include <functional>
#include <any>class EventBus {
public:using Handler = std::function<void(const std::any&)>;// 订阅事件void subscribe(const std::string& eventName, Handler handler) {handlers[eventName].push_back(handler);}// 发布事件void publish(const std::string& eventName, const std::any& data) {if (handlers.find(eventName) != handlers.end()) {for (auto& h : handlers[eventName]) {h(data);}}}private:std::unordered_map<std::string, std::vector<Handler>> handlers;
};
2. 使用示例
int main() {EventBus bus;// 订阅 int 事件bus.subscribe("score_update", [](const std::any& data) {if (auto p = std::any_cast<int>(&data)) {std::cout << "Score updated: " << *p << std::endl;}});// 订阅 string 事件bus.subscribe("message", [](const std::any& data) {if (auto p = std::any_cast<std::string>(&data)) {std::cout << "New message: " << *p << std::endl;}});// 订阅自定义类型事件struct Player {std::string name;int hp;};bus.subscribe("player_join", [](const std::any& data) {if (auto p = std::any_cast<Player>(&data)) {std::cout << "Player joined: " << p->name << ", HP=" << p->hp << std::endl;}});// 发布事件bus.publish("score_update", 100);bus.publish("message", std::string("Hello, EventBus!"));bus.publish("player_join", Player{"Alice", 150});return 0;
}
运行结果示例
Score updated: 100
New message: Hello, EventBus!
Player joined: Alice, HP=150
3.扩展示例
示例 1:广播多个监听者
bus.subscribe("message", [](const std::any& data) {if (auto p = std::any_cast<std::string>(&data)) {std::cout << "[Logger] " << *p << std::endl;}
});
bus.publish("message", std::string("Broadcast test"));
输出会触发 所有监听者:
New message: Broadcast test
[Logger] Broadcast test
示例 2:事件驱动的游戏逻辑
bus.subscribe("enemy_killed", [](const std::any& data) {if (auto p = std::any_cast<int>(&data)) {std::cout << "Enemy killed, ID=" << *p << ", add 10 points!\n";}
});
bus.publish("enemy_killed", 42);
输出:
Enemy killed, ID=42, add 10 points!
示例 3:异构事件队列
std::vector<std::pair<std::string, std::any>> eventQueue;
eventQueue.push_back({"score_update", 200});
eventQueue.push_back({"message", std::string("Queued event")});
eventQueue.push_back({"player_join", Player{"Bob", 120}});// 批处理所有事件
for (auto& [eventName, data] : eventQueue) {bus.publish(eventName, data);
}
输出:
Score updated: 200
New message: Queued event
Player joined: Bob, HP=120
小总结
std::any
让消息总线可以存储任意类型事件数据。- 取值时用
std::any_cast<T>
,类型安全。 - 适合事件系统、插件框架、异构数据流处理。
总结
std::any
= 存任意类型的单值容器(运行时类型检查)。- 存取用
std::any_cast<T>
,类型不匹配会抛异常。 - 适合 异构容器、插件系统、配置存储。
- 缺点是 性能不如
variant
,存取需小心类型。