Allocator 是 STL 中负责内存管理 的组件,核心作用是把容器的数据结构逻辑和底层内存操作解耦。它有四个职责:分配原始内存(allocate)、释放内存(deallocate)、在已有内存上构造对象(construct)、析构对象但不释放内存(destroy)。通过模板参数注入不同的 allocator,可以在不改容器代码的情况下切换内存策略。
当不使用allocator时,容器的内存操作是硬编码的:
cpp
// 伪代码:容器直接用 new/delete
template<typename T>
class Vector {
void push_back(const T& val) {
T* newbuf = new T[capacity * 2]; // 1. 申请内存 + 构造对象(绑死了)
// ...
delete[] oldbuf; // 2. 析构 + 释放内存(绑死了)
}
};
问题:内存分配和对象构造被 new 绑死了。 你没法:
- 用内存池(减少 malloc 开销)
- 用共享内存(进程间通信)
- 用自定义对齐(SIMD 场景)
- 统计内存使用
allocator 把"要内存"和"构造对象"拆开了。
cpp
template<typename T>
class allocator {
public:
// ① 类型定义
using value_type = T;
using pointer = T*;
using size_type = size_t;
// ② 内存分配(只申请裸内存,不构造对象!)
T* allocate(size_t n) {
return static_cast<T*>(::operator new(n * sizeof(T)));
}
// ③ 内存释放(只归还内存,不析构对象!)
void deallocate(T* p, size_t n) {
::operator delete(p);
}
// ④ 对象构造(在已有内存上 placement new)
void construct(T* p, const T& val) {
new(p) T(val); // placement new
}
// ⑤ 对象析构(只调析构,不释放内存)
void destroy(T* p) {
p->~T(); // 显式析构
}
};
allocate(n)--- 分配能容纳 n 个对象的原始内存,不调用构造函数deallocate(p, n)--- 释放之前分配的内存,不调用析构函数construct(p, args...)--- 在已分配的内存地址上用 placement new 构造对象destroy(p)--- 调用对象的析构函数,但不释放内存
这四步把"内存的生命周期"和"对象的生命周期"彻底分开了。容器可以先分配一大块内存(reserve),后面需要时再逐个构造对象,不用每次都 new/delete。
以vector::push_back为例:
cpp
template<typename T, typename Alloc = allocator<T>>
class vector {
T* start; size_t len, cap;
Alloc alloc; // 每个容器持有一个 allocator 实例
void push_back(const T& val) {
if (len == cap) {
T* newbuf = alloc.allocate(cap * 2); // 只要内存
for (size_t i = 0; i < len; i++)
alloc.construct(newbuf + i, start[i]); // 逐个构造
for (size_t i = 0; i < len; i++)
alloc.destroy(start + i); // 逐个析构旧对象
alloc.deallocate(start, cap); // 归还旧内存
start = newbuf;
cap *= 2;
}
alloc.construct(start + len, val); // 构造新元素
len++;
}
};
allocate 和 new 的区别(重要)
cpp
new T[n] = operator new(n*sizeof(T)) + n次构造函数
allocator = allocate(n) + construct(p, val)
↑ 只申请内存 ↑ 只构造对象
分开了!可以独立控制
以vector::reserve为例:
cpp
void reserve(size_t n) {
T* p = alloc.allocate(n); // 只要内存,不构造!
// 如果用 new T[n],会调用 n 次默认构造 → 完全多余
}
reserve 只要内存空间,不需要构造对象。用 new 会浪费 n 次默认构造开销。这就是 allocator 把分配和构造拆开的核心价值。
rebind机制
容器内部不只分配用户指定类型的内存。比如 list<int> 内部需要分配的是节点 _Node<int>,不是 int。rebind 就是让 allocator<int> 能"重新绑定"为 allocator<_Node<int>>,这样容器内部的各种辅助结构也能用同一套内存策略。
核心矛盾
cpp
// list 内部的真实节点结构
struct _List_node {
_List_node* prev;
_List_node* next;
int data; // ← T 在这里
};
容器需要分配的是 _List_node,不是 int。但用户只给了 allocator<int>,怎么拿到 allocator<_List_node>?
rebind工作模式
cpp
template<typename T>
class allocator {
public:
// rebind 是一个嵌套模板结构体
template<typename U>
struct rebind {
using other = allocator<U>; // "把 T 换成 U"
};
// ... allocate, deallocate 等
};
用法:
cpp
using AllocInt = allocator<int>;
using AllocNode = AllocInt::rebind<_List_node>::other;
// allocator<int> → allocator<_List_node>
rebind<_List_node>::other 就是 allocator<_List_node>------类型变了,但分配策略(内存池、自定义逻辑等)保持一致。
容器内部用法
以list为例:
cpp
template<typename T, typename Alloc = allocator<T>>
class list {
// 节点类型
struct _Node {
_Node* prev;
_Node* next;
T data;
};
// 关键!把 allocator<T> rebind 成 allocator<_Node>
using node_alloc = typename Alloc::template rebind<_Node>::other;
node_alloc alloc; // 实际用来分配节点的 allocator
_Node* create_node(const T& val) {
_Node* p = alloc.allocate(1); // 分配一个 _Node 大小的内存
alloc.construct(p, _Node{nullptr, nullptr, val}); // 构造节点
return p;
}
};
每种容器 rebind 到什么
| 容器 | 用户给的 T | 实际分配的类型 | 为什么不同 |
|---|---|---|---|
vector<T> |
T | T | 不需要 rebind,vector 直接存 T |
list<T> |
T | _List_node<T> |
节点带前后指针 |
map<K,V> |
pair<const K,V> |
_RB_tree_node<pair<const K,V>> |
红黑树节点带颜色+父子指针 |
unordered_map<K,V> |
pair<const K,V> |
哈希桶节点 | 带_next指针 |
string |
char | char | 不需要 rebind |
cpp
用户写:list<int, MyAllocator<int>>
MyAllocator<int>
│
▼ rebind
MyAllocator<_List_node<int>>
│
▼ allocate(1)
返回 _List_node<int>* 的裸内存
│
▼ construct(p, ...)
placement new 构造节点