<p>std::any 通过 void* 加函数表实现类型擦除,不依赖虚函数;其核心是构造/析构/拷贝/类型查询函数指针组成的结构体,配合小对象优化与严格生命周期管理。</p>

std::any 的核心是 void* + 类型擦除函数表
它不是靠虚函数继承实现的,而是用一个 struct 存函数指针:构造、析构、拷贝、类型查询。所有操作都通过这个“函数表”跳转,std::any 本身只管存 void* 和这个表指针。
实操建议:
- 自己写简易版时,别直接裸存
void*,一定要配对管理生命周期——比如用new分配的,就得在析构函数里delete;否则一 move 或赋值就悬空 - 类型查询靠
std::type_info,但注意:不同编译单元的同名类(尤其模板实例化)可能生成不同std::type_info对象,typeid(T) == typeid(U)不一定稳,优先用std::any_cast的运行时检查 - 小对象优化(SOO)很关键:像 libstdc++ 和 libc++ 都给
std::any预留了约 24–32 字节内联空间,避免小类型(如int、std::string_view)堆分配;自己实现时漏掉这点,性能会差一截
std::function 的类型擦除比 std::any 更重
std::function 不仅要擦除目标类型,还要统一调用签名——所有可调用体(函数指针、lambda、std::bind、仿函数)都被转换成“能响应 operator() 并匹配指定参数/返回类型的黑盒”。
常见错误现象:
立即学习“C++免费学习笔记(深入)”;
- 捕获局部变量的 lambda 赋给
std::function后,原作用域结束,调用时报segmentation fault——本质是擦除后没做所有权转移,得确保捕获的内容生命周期 ≥std::function本身 - 用
std::function<void></void>接收返回int的函数,编译失败:擦除前就做签名匹配,不兼容的 callable 根本进不来,不是运行时报错 - 移动后调用已移动的
std::function,触发std::bad_function_call——因为擦除函数表里“调用函数指针”被置为 nullptr,但这个状态不会自动清零其他字段,内存未必全脏,所以不能靠 memcmp 判空
用 std::any_storage 和 std::function 的 vtable 手写简易版
没必要从零造轮子,但理解它们怎么组织数据,能避开很多误用。
实操建议:
- 定义擦除结构体时,把函数指针打包进一个
struct,比如:struct any_vtable { void (*dtor)(void*); void (*copy)(void*, const void*); const std::type_info& (*type)(void*); };别把每个函数单独存为成员变量,缓存不友好,也难扩展 - lambda 擦除进
std::function时,编译器会生成一个唯一闭包类型,并把捕获数据作为该类型的成员;若捕获为空,有些实现(如 MSVC)会直接退化为函数指针,但别依赖——sizeof(std::function<int>)</int>在不同 STL 实现里可能差 8~16 字节 - 别在
std::any里存多态对象(带虚函数的类),除非你手动保证虚表指针有效;更安全的做法是存智能指针或值语义类型
std::any_cast 和 std::function::target 的陷阱
这两个接口看着像“取出来”,实际都有隐含约束和未定义行为风险。
使用场景与坑点:
-
std::any_cast<t>(&a)</t>返回T*,如果a里不是T类型,返回空指针——但很多人写成*std::any_cast<t>(&a)</t>,一解引用就崩;应该先判空再用 -
std::function::target<t>()</t>只能取回原始类型,不能跨继承体系;比如存的是std::unique_ptr<base>,用target<:unique_ptr>></:unique_ptr>拿不到,哪怕Derived继承Base -
std::function的target_type()返回std::type_info,但 C++17 起禁止用==直接比较两个std::type_info对象(标准说“行为未定义”),正确方式是用std::type_info::hash_code()或std::type_info::name()(后者不跨平台)
类型擦除不是魔法,它把编译期信息搬到运行期管理,代价是间接跳转、额外内存、以及所有生命周期必须人工对齐。最常被忽略的,其实是那个“谁负责释放”的契约——擦除层不管,得你自己盯紧。










