C++ 专家(15):分配器深入——PMR、内存池、arena 分配器、scoped_allocator
更新时间:2026-09-01。本文是
languages/cpp/expert/专家层第 15 篇,接 Ranges 深入。C++17 引入了<memory_resource>,提供了标准化的多态分配器接口。PMR 可以运行时切换分配策略,适合在游戏、嵌入式、实时系统中使用。
本文要回答的问题
- PMR 的
std::pmr::memory_resource接口是什么? monotonic_buffer_resource怎么用?为什么适合短期分配?pool_resource和unsynchronized_pool_resource有什么区别?scoped_allocator_adaptor是什么?嵌套容器怎么统一分配器?
一、PMR 核心接口
cpp
#include <memory_resource>
// 所有 PMR 分配器的基类
class memory_resource {
public:
void* allocate(size_t bytes, size_t alignment = alignof(max_align_t));
void deallocate(void* p, size_t bytes, size_t alignment = alignof(max_align_t));
bool is_equal(const memory_resource& other) const noexcept;
private:
virtual void* do_allocate(size_t bytes, size_t alignment) = 0;
virtual void do_deallocate(void* p, size_t bytes, size_t alignment) = 0;
virtual bool do_is_equal(const memory_resource& other) const noexcept = 0;
};二、monotonic_buffer_resource
cpp
// 单调缓冲区分配器:只分配,不释放单个对象
// 一次性释放整个缓冲区,非常快
char buffer[1024 * 1024]; // 1MB 栈上缓冲区
std::pmr::monotonic_buffer_resource pool(buffer, sizeof(buffer));
// 使用 PMR 容器
std::pmr::vector<int> vec(&pool);
vec.push_back(1);
vec.push_back(2);
// 所有分配都在 buffer 中,不需要 deallocate
// 也可以动态增长
std::pmr::monotonic_buffer_resource pool2(1024); // 初始 1KB,不够自动 new适用场景: 短期任务(如游戏帧处理、请求处理),任务开始时用缓冲区,结束后整体释放,没有内存碎片。
三、pool_resource
cpp
// pool_resource:按大小分类的内存池,释放后可以复用
// 线程安全版本
std::pmr::synchronized_pool_resource pool;
// 非线程安全版本
std::pmr::unsynchronized_pool_resource pool;
// 使用
std::pmr::vector<int> vec(&pool);
std::pmr::string str(&pool);
// 配置
std::pmr::pool_options opts;
opts.max_blocks_per_chunk = 256; // 每个 chunk 最大块数
opts.largest_required_pool_block = 1024; // 最大池化块大小
std::pmr::synchronized_pool_resource pool(opts, &upstream);pool_resource 结构: 内部维护多个大小分类的池(如 8 字节、16 字节、32 字节...),分配时按大小对齐到最近的池,释放时放回池里。
四、多级分配器组合
cpp
// 多级分配器:先分配在栈上缓冲区,不够走池,再不够走 new
char buffer[1024];
std::pmr::monotonic_buffer_resource first(buffer, sizeof(buffer));
std::pmr::unsynchronized_pool_resource second(&first); // 上游是 first
std::pmr::vector<int> vec(&second);上游链: vec → second (pool) → first (buffer) → operator new
五、scoped_allocator_adaptor
cpp
// 嵌套容器:vector<pair<string, int>>
// 默认:vector 的分配器不会传递给 string
// 用 scoped_allocator_adaptor 传递分配器
using Alloc = std::scoped_allocator_adaptor<std::pmr::polymorphic_allocator<char>>;
using String = std::basic_string<char, std::char_traits<char>, Alloc>;
using Vector = std::vector<String, Alloc>;
std::pmr::monotonic_buffer_resource pool(4096);
Alloc alloc(&pool);
Vector vec(alloc);
vec.push_back("hello"); // string 也会从 pool 分配
vec.push_back("world"); // 所有内存都在同一个池中六、自定义 memory_resource
cpp
// 自定义分配器:统计分配次数
class StatsResource : public std::pmr::memory_resource {
std::pmr::memory_resource* upstream;
size_t alloc_count = 0;
size_t total_bytes = 0;
public:
StatsResource(std::pmr::memory_resource* upstream) : upstream(upstream) {}
size_t allocations() const { return alloc_count; }
size_t total() const { return total_bytes; }
private:
void* do_allocate(size_t bytes, size_t alignment) override {
alloc_count++;
total_bytes += bytes;
return upstream->allocate(bytes, alignment);
}
void do_deallocate(void* p, size_t bytes, size_t alignment) override {
upstream->deallocate(p, bytes, alignment);
}
bool do_is_equal(const memory_resource& other) const noexcept override {
return this == &other;
}
};七、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| monotonic_buffer_resource 的 buffer 用完 | 自动走 upstream new | 预料到不够时提前分配更大 buffer |
| pool_resource 内存不回收到 OS | 长期运行内存占用高 | 定期释放或重新创建 pool |
| scoped_allocator_adaptor 类型不匹配 | 编译错误 | 确保所有嵌套容器都使用同一分配器 |
| 线程安全用错 | 数据竞争 | 多线程用 synchronized_pool_resource |
相关与延伸
下一篇:SIMD 与自动向量化——编译器如何优化循环;allocator 入门,见 自定义分配器。
一句话总结
C++ PMR 分配器深入:memory_resource 是多态分配器基类;monotonic_buffer_resource 只分配不释放,适合短期任务,一次性清除;pool_resource 按大小分类池化,释放后可复用,适合频繁分配小对象;scoped_allocator_adaptor 把分配器传递给嵌套容器,所有内存来自同一池;可以自定义 memory_resource 实现统计、日志、调试分配器;多级分配器组合:栈上 buffer → pool → operator new。