Allocator 是 C++ 容器与内存分配行为之间的契约接口,容器通过其 allocate()/deallocate() 等成员间接管理内存,从而支持栈分配、共享内存等定制策略,而非直接调用 new/delete。

Allocator 是什么,为什么不能直接 new/delete
Allocator 不是“高级内存池”,它是 C++ 容器(比如 std::vector、std::map)和内存分配行为之间的契约接口。容器不直接调用 new 或 malloc,而是通过 Allocator 的 allocate() 和 deallocate() 去拿/还内存——这样你才能在不改容器代码的前提下,把 std::vector 的内存挪到共享内存、栈上、或带对齐要求的缓冲区里。
常见错误现象:std::vector 编译失败,但没报错在 MyAlloc 上,而是卡在 std::vector 内部某个 construct() 调用里——因为漏实现了 construct()/destroy(),或者没满足 Allocator 的“可交换性”(即两个同类型 Allocator 实例必须 == 返回 true)。
- 必须定义
value_type、pointer、const_pointer等 10+ 个 typedef,C++17 起可用std::allocator_traits补缺,但基础骨架不能少 - 所有成员函数必须是
noexcept(尤其deallocate()),否则某些容器(如std::deque)可能拒绝编译 - 不要在
allocate()里抛异常;标准要求它只负责分内存,构造对象由容器后续调用construct()完成
写一个最简可用的自定义 Allocator(以栈分配为例)
想让 std::vector 优先用栈内存,超限时再 fallback 到堆——这不是优化噱头,而是嵌入式或实时场景的真实需求。关键不是“多快”,而是“确定性”:栈分配无锁、无系统调用、时间可控。
下面这个 StackAllocator 支持固定大小(N 字节)的栈缓冲,只用于 int 类型(泛化需用模板偏特化或 std::allocator_traits):
立即学习“C++免费学习笔记(深入)”;
templatestruct StackAllocator { using value_type = T; T* buffer_[N / sizeof(T)]; size_t used_ = 0; T allocate(size_t n) { if (n sizeof(T) <= N - used_ sizeof(T)) { T p = reinterpretcast
(buffer ) + used; used += n; return p; } return static_cast>(::operator new(n sizeof(T))); } void deallocate(T p, size_t n) { if (p >= reinterpret_cast
>(buffer_) && p < reinterpretcast (buffer ) + N / sizeof(T)) return; // 栈内存不释放 ::operator delete(p); }template
struct rebind { using other = StackAllocator; }; bool operator==(const StackAllocator&) const noexcept { return true; } bool operator!=(const StackAllocator&) const noexcept { return false; } };
使用场景:std::vector —— 最多存 64 个 int 在栈上,再多就自动切到堆。
- 必须实现
rebind,否则std::map会编译失败(内部节点类型不同,需要转成对应> StackAllocator) -
operator==必须返回true:标准假定同类型 Allocator 可互换,否则容器 move 构造或 swap 时行为未定义 - 没实现
construct()/destroy()?C++11 起可依赖默认版本,但若要支持 placement-new 异常安全,还是得显式写
allocator_traits 怎么帮你少写几十行样板代码
手写完整 Allocator 很容易漏掉 max_size()、select_on_container_copy_construction() 这类冷门接口,而 std::allocator_traits 就是干这个的:它给你的 Allocator “打补丁”,提供合理默认实现。
比如你只写了 allocate()/deallocate(),其他全靠 traits:
templatestruct MinimalAlloc { using value_type = T; T* allocate(size_t n) { return static_cast (malloc(n * sizeof(T))); } void deallocate(T* p, size_t) { free(p); } bool operator==(const MinimalAlloc&) const noexcept { return true; } bool operator!=(const MinimalAlloc&) const noexcept { return false; } }; // 使用时: std::vector
> v; // traits 自动补全 construct/destroy/max_size/select_on_copy 等
性能影响:零开销。traits 全是 constexpr 静态函数,编译期就内联了。
- 仍需手动提供
value_type和operator==,这是 traits 无法推导的底线 -
select_on_container_copy_construction()默认返回 *this,但若你 Allocator 持有状态(如 arena ID),就得重写它来隔离副本 - C++20 起
allocator_traits::allocate支持传const std::pmr::polymorphic_allocator,但普通自定义 Alloc 不涉及 PMR 时不用管&
哪些容器真的会用到 Allocator,哪些只是“摆设”
std::string、std::vector、std::deque、std::list、std::map 都尊重 Allocator;但 std::array 不用(编译期大小固定),std::optional、std::variant 也不用(内部不动态分配)。最容易被忽略的是:即使你写了 Allocator,std::vector::reserve() 仍可能触发多次 allocate() + deallocate() + construct(),因为扩容时旧内存要整体搬移。
兼容性坑:
- MSVC 2019 以前对非标准 Allocator 的
std::vector::shrink_to_fit()支持不全,可能静默退回到默认分配器 - libc++(macOS/iOS)要求 Allocator 的
deallocate()必须能安全接受 null 指针;libstdc++(Linux GCC)不强制,但写成if (p) ::operator delete(p);更稳妥 -
std::unordered_map的桶数组分配行为因实现而异:GCC 用allocator_traits::allocate,Clang 可能绕过 traits 直接调allocate(),所以别省略rebind
真正难的不是写 allocator,是让它和所有容器、所有 STL 版本、所有编译器都安静共处——哪怕只漏一个 operator==,也可能在某次 release 构建中才暴露。









