std::any 的性能开销:基于 libstd++ 源码分析
C++17 中引入了 std::any,可以非常方便地将任意类型的变量放到其中,做到安全的类型擦除。然而万物皆有代价,这种灵活性背后必然伴随着性能取舍。
std::any 的实现本身也并不复杂,本文将基于 libstd++ 标准库源码 深入解析其实现机制与性能开销。

底层存储
std::any 需要解决的核心问题在于:
- 异构数据存储:如何统一管理不同尺寸的对象
- 类型安全访问:如何在擦除类型信息后仍能提供安全的类型查询。例如可以直接通过 std::any 提供的 type() 函数,直接获取到底层数据的类型信息。
从 libstd++ 源码中提取的关键类结构如下
1 | class any { |
可以看到有两个核心变量:
_M_storage:负责存储数据值本身或者指针。_M_manager:函数指针,负责指向具体类型 template class 的实现,其中包含了类型信息。
我们先看 _M_storage 的实现:
1 | union _Storage |
_Storage 类是一个 union 实现。里面包含两个属性:_M_ptr 和长度为 sizeof(_M_ptr) 的 char 数组 _M_buffer。即长度为指针大小,在 64 位机器下,_M_buffer 的长度是 8。
那么,在什么情况下分别使用 _M_ptr 和 _M_buffer 呢?主要通过以下模板变量进行编译期决策。
1 | template<typename _Tp, typename _Safe = is_nothrow_move_constructible<_Tp>, bool _Fits = (sizeof(_Tp) <= sizeof(_Storage)) && (alignof(_Tp) <= alignof(_Storage))> |
简单来说:_Tp 可以无异常移动构造 && _Tp 能完全放入 _Storage 中 。
这是一个非常典型的 SOO(Small Object Optimization 小对象优化)。即:对于小尺寸对象,直接在容器自身的连续内存中 (通常为栈内存) 完成存储,这样可以避免在堆上开辟新的内存。
因此:
- 对于小尺寸对象(≤指针大小),直接在
_M_buffer中通过 placement new 创建对象。避免堆内存分配带来的性能开销,提升 CPU 缓存局部性(对高频访问的场景尤为重要)。 - 对于大尺寸对象,直接在堆上通过 new 申请内存,
_M_storage存储对应的指针。
但这个内存结构的设计,也存在着潜在的内存浪费:union 的内存等于最大字段的内存,因此即使在 std::any 中存储 1 字节的 char 类型变量,_M_storage 也需要 8 字节。
另外,我们发现在 _Storage 并未存储任何类型信息。但我们可以通过 std::any 的 type() 函数获取到对应的类型信息。这是如何做到呢?
接下来,我们看 _M_manager 的实现:
std::any 的做法非常巧妙,将所有需要类型信息的操作,都通过一个 template class 的 static 函数来实现。std::any 对象中只存储这个函数的指针,即 void (*_M_manager)(_Op, const any*, _Arg*)。
1 | template<typename _Tp> |