std::any足够且安全,支持任意可复制/移动类型,无需手动内存管理;常见错误是绕过它自封装、错误cast或存不可复制类型,应优先用引用版any_cast配合类型检查。

用 std::any 就够了,别自己造轮子
标准库的 std::any 就是为此设计的:类型安全、支持任意可复制类型、无需手动管理内存。它内部用类型擦除实现,比手写 void* + type_info 组合靠谱得多,也比自己维护 union + 枚举 + 拷贝构造逻辑简单干净。
常见错误是试图绕过 std::any 自己封装“更轻量”的版本——结果要么漏掉移动语义,要么在 std::string 或自定义类上触发未定义行为,要么在异常抛出路径中资源泄漏。
-
std::any要求存储类型必须满足CopyConstructible(C++17 起也支持MoveConstructible) - 不能存数组类型(如
int[5]),也不能存抽象类对象(但可以存其指针或智能指针) - 性能开销主要在小对象优化(small object optimization)失效时——比如存一个 64 字节的结构体,可能触发堆分配
std::any_cast 失败时会抛 std::bad_any_cast
这是最常踩的坑:把 std::any 当成万能容器乱 cast,不检查类型就强转。比如存的是 double,却用 std::any_cast<int></int> 去取,程序直接终止。
正确做法永远优先用引用版 std::any_cast<T&> 配合 std::any::has_value() 或 try-catch,而不是依赖返回指针的版本做空判断。
立即学习“C++免费学习笔记(深入)”;
- 安全写法:
if (a.type() == typeid(std::string)) { auto& s = std::any_cast<std::string&>(a); } - 不推荐:
auto* p = std::any_cast<int>(&a); if (p) { ... }—— 这种写法掩盖了类型不匹配的本质问题 - 如果确实需要运行时试探多种类型,建议用
std::visit+std::variant替代,std::any不适合这种分支密集场景
想存不可复制类型?得换思路
std::any 明确要求类型可复制(或至少可移动)。如果你非要塞 std::unique_ptr<T>、带非平凡析构的类、或者临时 std::function 对象,没问题——它们都满足移动构造;但像栈上裸指针、std::atomic、或删掉了拷贝/移动操作符的类,就不能直接塞。
这时候不是改 std::any 实现,而是该问:这个值真的需要被“持有”吗?还是只需要“访问”?
- 若只是传递引用,用
std::any存T&是错的(引用不能复制),应改用std::reference_wrapper<T> - 若类型确实不可移动也不可复制(极少见),只能用
std::shared_ptr包一层再存,代价是额外一次堆分配和引用计数 - 别给
std::any加noexcept保证——它的构造/赋值/析构都不承诺不抛异常,尤其当 T 的构造函数可能抛异常时
兼容性与编译器支持要注意
std::any 是 C++17 引入的,所有主流编译器从那之后都支持,但老项目如果还在用 C++14,强行启用 -std=c++17 可能暴露其他兼容问题(比如 std::optional、std::string_view 的隐式转换变化)。
另外,MSVC 在早期 15.7 版本对 std::any 的 SSO(小对象优化)实现有 bug,导致某些 24 字节内的类型仍触发堆分配;GCC 7.1+ 和 Clang 5.0+ 表现稳定。
- 检查是否可用:
#ifdef __cpp_lib_any,而不是只看__cplusplus宏 - 不要依赖
sizeof(std::any)的具体值——不同标准库实现差异大,MSVC 是 32 字节,libstdc++ 是 48 字节 - 调试时注意:GDB 对
std::any的打印支持有限,print a.type().name()比直接print a更可靠
事情说清了就结束









