在 C++17 标准库中引入的 std::any 提供了一种类型擦除的容器,能够在运行时保存任意类型的对象。虽然 std::any 的语法相对简单,但若使用不当仍会导致性能损失、类型安全问题以及异常安全缺陷。下面从使用场景、类型安全、异常安全以及性能优化四个角度系统讲解如何安全、高效地使用 std::any。
1. 基础使用
#include <any>
#include <iostream>
#include <string>
int main() {
std::any a = 42; // 存储整型
std::any b = std::string("C++"); // 存储字符串
try {
std::cout << std::any_cast<int>(a) << '\n';
std::cout << std::any_cast<std::string>(b) << '\n';
} catch(const std::bad_any_cast& e) {
std::cerr << "类型不匹配: " << e.what() << '\n';
}
}
2. 确保类型安全
2.1 预先查询类型
使用 any.type() 可以在尝试访问前检查当前存储的类型。
if (a.type() == typeid(int)) {
std::cout << std::any_cast<int>(a);
}
2.2 自定义包装类
为常用的 any 用法提供封装,内部维护一个 std::type_index 的映射,避免多次 typeid 比较。
class SafeAny {
public:
template<typename T>
void set(const T& value) {
data_ = value;
type_ = typeid(T);
}
template<typename T>
T get() const {
if (type_ != typeid(T)) throw std::bad_any_cast();
return std::any_cast <T>(data_);
}
private:
std::any data_;
std::type_index type_{typeid(void)};
};
3. 异常安全
3.1 防止析构时抛异常
std::any 的内部销毁会调用存储对象的析构函数。如果该析构函数抛异常,整个程序会终止。确保存储的对象是 noexcept 析构。
struct Safe {
~Safe() noexcept { /* safe cleanup */ }
};
3.2 使用 std::any::reset() 替代直接赋值
直接赋值会先析构旧值,然后构造新值;如果构造抛异常,旧值已被销毁。reset() 先拷贝新值,再销毁旧值,提供了更好的异常安全。
std::any a = 10;
a = 20; // 旧值已被析构
a.reset(20); // 先拷贝再析构
4. 性能优化
4.1 避免不必要的复制
`std::any_cast
(a)` 会复制存储对象,若对象较大,可使用引用: “`cpp try { auto& ref = std::any_cast(b); ref += ” 2026″; } catch(const std::bad_any_cast&) { /* 处理错误 */ } “` ### 4.2 预分配空间 当已知 `any` 将存储的类型时,直接使用 `std::any::emplace (…)` 可以避免多次分配: “`cpp std::any a; a.emplace>(10, 0); // 预分配10个0 “` ### 4.3 减少动态分配 `std::any` 内部会为每个对象分配堆内存。若需要频繁存取相同类型的数据,考虑使用 `std::variant` 或自定义类型包装,避免堆分配。 ## 5. 实战案例:事件系统 “`cpp // 事件基类 struct Event { virtual ~Event() noexcept = default; }; // 具体事件 struct ClickEvent : Event { int x, y; }; struct KeyEvent : Event { char key; }; // 事件处理器 class EventDispatcher { public: void dispatch(const std::any& e) { if (e.type() == typeid(ClickEvent)) { auto ce = std::any_cast(e); handle(ce); } else if (e.type() == typeid(KeyEvent)) { auto ke = std::any_cast(e); handle(ke); } else { std::cerr