C++ 进阶(30):自定义分配器——分配器概念、状态分配器、STL 容器分配器
更新时间:2026-09-01。本文是
languages/cpp/intermediate/进阶第 30 篇,接 Ranges 基础。STL 容器默认用std::allocator,但可以替换成自己的分配器。自定义分配器可以优化内存布局、减少碎片、支持内存池。
本文要回答的问题
- 分配器是什么?容器怎么用分配器分配和释放内存?
- 怎么实现一个简单的自定义分配器?
- 有状态的分配器是什么意思?C++11 后分配器可以带状态?
- PMR 和 scoped_allocator 是什么?
一、分配器概念
cpp
// 分配器是容器的"内存工厂",负责分配和释放
template<typename T>
struct Allocator {
using value_type = T;
// 分配 n 个元素的内存
T* allocate(size_t n) {
return static_cast<T*>(::operator new(n * sizeof(T)));
}
// 释放内存
void deallocate(T* p, size_t n) {
::operator delete(p);
}
// 构造对象(C++17 后可选,用 std::allocator_traits)
template<typename... Args>
void construct(T* p, Args&&... args) {
new (p) T(std::forward<Args>(args)...);
}
// 析构对象
void destroy(T* p) {
p->~T();
}
};二、自定义分配器:内存池
cpp
template<typename T>
class PoolAllocator {
struct Block {
alignas(alignof(T)) char data[sizeof(T)];
};
std::vector<Block> pool;
std::vector<Block*> free_list;
public:
using value_type = T;
PoolAllocator() = default;
T* allocate(size_t n) {
if (n != 1) {
// 大块分配直接走 new
return static_cast<T*>(::operator new(n * sizeof(T)));
}
if (free_list.empty()) {
// 池里没有空闲块,分配新块
pool.emplace_back();
free_list.push_back(&pool.back());
}
auto ptr = free_list.back();
free_list.pop_back();
return reinterpret_cast<T*>(ptr);
}
void deallocate(T* p, size_t n) noexcept {
if (n != 1) {
::operator delete(p);
return;
}
// 把块放回空闲列表
auto* block = reinterpret_cast<Block*>(p);
free_list.push_back(block);
}
};三、分配器在容器中使用
cpp
// 使用自定义分配器
std::vector<int, PoolAllocator<int>> vec;
vec.push_back(1);
vec.push_back(2);
// 所有分配走 PoolAllocator
// 字符串分配器
using PoolString = std::basic_string<char, std::char_traits<char>, PoolAllocator<char>>;
std::vector<PoolString, PoolAllocator<PoolString>> vec2;四、有状态分配器
C++11 之前分配器不能有状态(所有实例必须相等)。C++11 之后分配器可以带状态:
cpp
template<typename T>
class ArenaAllocator {
char* arena;
size_t used;
size_t size;
public:
using value_type = T;
using propagate_on_container_copy_assignment = std::true_type;
using propagate_on_container_move_assignment = std::true_type;
using propagate_on_container_swap = std::true_type;
ArenaAllocator(char* arena, size_t size)
: arena(arena), used(0), size(size) {}
T* allocate(size_t n) {
if (used + n * sizeof(T) > size)
throw std::bad_alloc();
auto ptr = arena + used;
used += n * sizeof(T);
return reinterpret_cast<T*>(ptr);
}
void deallocate(T*, size_t) noexcept {
// 不做任何事,线性分配器不释放单个元素
}
};五、PMR(C++17)
cpp
#include <memory_resource>
// 使用 PMR 的单调缓冲区分配器
char buffer[1024];
std::pmr::monotonic_buffer_resource pool(buffer, sizeof(buffer));
// PMR 容器用 polymorphic_allocator
std::pmr::vector<int> vec(&pool);
vec.push_back(1);
vec.push_back(2);
// 所有分配都在 buffer 里,非常快
// 不需要释放,pool 的析构函数释放整个缓冲区六、分配器的作用
| 分配器 | 用途 |
|---|---|
| 默认 allocator | 通用场景,new/delete |
| 内存池 allocator | 频繁分配小对象,减少碎片 |
| 线性分配器 | 一次分配,一起释放,游戏/嵌入式 |
| PMR 单调缓冲 | 高性能短期分配,C++17 标准 |
| 自定义对齐 | 对齐到缓存行,避免伪共享 |
七、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 分配器 rebind 没实现 | 容器不能分配不同类型 | 用 allocator_traits 自动处理 |
| 分配器状态不传递 | 容器拷贝后的分配器指向不同内存池 | 设置 propagate_on_container_copy |
| PMR 能用但别在运行时切换 | 分配器类型不同,容器类型不同 | 运行时多态用 std::pmr::polymorphic_allocator |
相关与延伸
下一篇:C++17/20 新特性总结——实用新特性一览;分配器深入,见 PMR 深入。
一句话总结
C++ 自定义分配器:分配器是容器的内存工厂,提供 allocate/deallocate/construct/destroy;自定义分配器可以实现内存池、线性分配器、对齐分配器;C++11 后分配器支持状态,通过 propagate_on_container_xxx 控制拷贝/移动/交换时行为;C++17 的 PMR 提供 monotonic_buffer_resource 和 polymorphic_allocator,适合短期高性能分配。