new/delete在频繁小对象分配时变慢,因每次需系统调用、堆管理及碎片处理;内存池通过预分配+空闲链表+显式构造/析构提升性能,但要求对象大小固定且需手动管理生命周期。

为什么 new/delete 在频繁小对象分配时会变慢
因为每次 new 都要向操作系统申请内存页、维护堆结构、处理碎片,delete 还要合并空闲块——这些操作在高频调用下开销远超对象构造本身。尤其当对象大小固定(如 Node、Event)且生命周期短时,堆管理成了瓶颈。
内存池把“分配”从系统级降为指针偏移+链表摘取,避免锁竞争和系统调用。但不是所有场景都适合:对象大小必须相对固定,否则池内空间浪费严重;且需自行管理对象生命周期,不能依赖 RAII 自动析构。
手写简易内存池的三个关键点
一个实用的内存池至少得解决:内存预分配、快速分配/回收、对象正确构造/析构。下面是最小可行实现的核心逻辑:
- 用
std::vector<char></char>或malloc一次性申请大块原始内存,按固定block_size切成槽位 - 用单向空闲链表(free list)管理可用块:每个空闲块头部存下一个空闲块地址,
allocate()只需取头、更新头指针 - 分配后必须显式调用
new (ptr) T(args...)定位构造;回收前必须显式调用obj->~T()析构,再归还指针到空闲链表
示例片段:
立即学习“C++免费学习笔记(深入)”;
class SimplePool {
char* pool_;
size_t block_size_;
char* free_list_;
public:
SimplePool(size_t n, size_t block_size)
: block_size_(block_size), free_list_(nullptr) {
pool_ = static_cast<char*>(malloc(n * block_size));
for (size_t i = 0; i < n - 1; ++i) {
char* p = pool_ + i * block_size;
*reinterpret_cast<char**>(p) = p + block_size; // 存下一个空闲地址
}
*reinterpret_cast<char**>(pool_ + (n-1)*block_size) = nullptr;
free_list_ = pool_;
}
void* allocate() {
if (!free_list_) return nullptr;
void* ptr = free_list_;
free_list_ = *reinterpret_cast<char**>(free_list_);
return ptr;
}
void deallocate(void* ptr) {
*reinterpret_cast<char**>(ptr) = free_list_;
free_list_ = static_cast<char*>(ptr);
}
};std::pmr::memory_resource 与自定义资源适配
C++17 引入 std::pmr::memory_resource 提供统一接口,但默认的 std::pmr::monotonic_buffer_resource 不支持回收(只增不减),std::pmr::synchronized_pool_resource 才是真正可复用的池——但它内部管理多个不同尺寸的子池,对齐和分块策略较复杂。
实际使用建议:
- 若只需单尺寸对象,自己实现比用
std::pmr::synchronized_pool_resource更轻量、更可控 - 若已用
std::pmr::vector等容器,可通过传入自定义std::pmr::memory_resource*重定向其内存来源,但注意:容器内元素仍走 placement new,析构必须由容器触发 -
std::pmr::polymorphic_allocator是包装器,它不管理内存,只是把allocate/deallocate转发给底层 resource,别误以为它自带池逻辑
容易被忽略的坑:析构与线程安全
内存池本身不关心对象语义,所以最容易出错的是忘记析构或重复析构:
- 对象析构函数没被调用 → 资源泄漏(文件句柄、内存、锁)
- 同一块内存被
placement new多次而未析构 → 析构函数可能访问非法状态 - 多线程并发
allocate/deallocate→ 必须加锁(比如用std::atomic操作 free list 头指针),但锁会抵消部分性能收益
如果对象有非平凡析构函数,务必在 deallocate 前显式调用;若不确定是否需要析构,可加 static_assert(std::is_trivially_destructible_v<t>)</t> 编译期拦截。
真正麻烦的不是分配快慢,而是把对象生命周期和内存生命周期彻底解耦后,谁负责在何时析构——这没法靠工具自动检查,只能靠设计约束和代码审查守住。


















