Allocator 是 C++ STL 容器内存管理的核心。它直接决定程序的内存效率和性能瓶颈。默认的 std::allocator 在高性能场景下往往不够用,但记住我的铁律:简单第一,复杂是敌人。
1. Allocator 的核心接口
STL 容器通过 allocator 抽象内存管理,解耦内存分配与容器逻辑。一个合格的 allocator 必须满足 allocator_traits 的要求:
pointer allocate(size_t n): 分配 n 个元素内存,返回 pointer。void deallocate(pointer p, size_t n): 释放内存。rebind<U>::other: 用于类型转换。
重要:C++17 起,construct 和 destroy 成员函数已废弃。C++23 标准中已完全移除。使用 std::construct_at 和 std::destroy_at 代替。
1 2 3 4 5 6 7 8 9 10 11 12
| #include <memory>
template <typename U, typename... Args> void construct(U* p, Args&&... args) { std::construct_at(p, std::forward<Args>(args)...); }
template <typename U> void destroy(U* p) { std::destroy_at(p); }
|
2. Stateful vs Stateless
Allocator 可以是 stateless(无状态,默认 std::allocator)或 stateful(有状态,带额外数据如内存池)。
关键:对于有状态的 allocator,不同实例必须能够通过 operator== 进行比较,否则容器的行为是未定义的。
3. Propagation Traits
容器在拷贝、移动或交换时,allocator 的行为由 propagation traits 控制:
1 2 3 4 5 6 7 8
| template <typename T> class MyAllocator { public: 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; using is_always_equal = std::true_type; };
|
propagate_on_container_copy_assignment: true 时,拷贝赋值会替换目标容器的 allocator。propagate_on_container_move_assignment: true 时,移动赋值会转移 allocator。propagate_on_container_swap: true 时,交换容器会交换 allocator。is_always_equal: true 时,所有 allocator 实例都相等(无状态),容器可以优化。
4. 自定义 Allocator 示例
4.1 简单无状态 Allocator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #include <memory> #include <vector>
template <typename T> class SimpleAllocator { public: using value_type = T;
template <typename U> struct rebind { using other = SimpleAllocator<U>; };
T* allocate(std::size_t n) { return static_cast<T*>(::operator new(n * sizeof(T))); }
void deallocate(T* p, std::size_t n) { ::operator delete(p); }
template <typename U, typename... Args> void construct(U* p, Args&&... args) { std::construct_at(p, std::forward<Args>(args)...); }
template <typename U> void destroy(U* p) { std::destroy_at(p); } };
std::vector<int, SimpleAllocator<int>> vec{SimpleAllocator<int>{}}; vec.emplace_back(42);
|
4.2 Arena Allocator(区域分配器)
Arena allocator 从预分配的大块内存中线性分配,适合高性能场景:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #include <memory> #include <vector>
template <typename T> class ArenaAllocator { public: using value_type = T;
ArenaAllocator(std::size_t arenaSize) : arena_(static_cast<char*>(::operator new(arenaSize))) , end_(arena_ + arenaSize) , current_(arena_) {}
~ArenaAllocator() { ::operator delete(arena_); }
template <typename U> struct rebind { using other = ArenaAllocator<U>; };
T* allocate(std::size_t n) { std::size_t bytes = n * sizeof(T); std::size_t aligned = (bytes + alignof(T) - 1) & ~(alignof(T) - 1); if (current_ + aligned > end_) { throw std::bad_alloc{}; } T* result = reinterpret_cast<T*>(current_); current_ += aligned; return result; }
void deallocate(T*, std::size_t) noexcept { }
void reset() noexcept { current_ = arena_; }
private: char* arena_; char* end_; char* current_; };
|
使用场景:游戏引擎、编译器、网络服务器等需要大量临时对象的场景。
5. C++17 PMR:多态内存资源
PMR 是 C++17 的杀手锏:多态 allocator。核心是 memory_resource 抽象基类。
std::pmr::memory_resource: 虚拟接口。std::pmr::polymorphic_allocator<T>: 使用 resource 的多态 allocator。- 内置实现:
monotonic_buffer_resource(单向,快)、synchronized_pool_resource、unsynchronized_pool_resource、new_delete_resource()。
优势:
- 运行时切换策略,无需模板膨胀。
- 池化减少分配开销。
- 线程安全选项。
5.1 示例:Monotonic Buffer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <memory_resource> #include <vector> #include <print>
int main() { char buffer[1024]; std::pmr::monotonic_buffer_resource resource(buffer.data(), buffer.size(), std::pmr::new_delete_resource()); std::pmr::polymorphic_allocator<int> alloc{&resource}; std::pmr::vector<int> vec{alloc}; for (int i = 0; i < 100; ++i) { vec.emplace_back(i * 2); } std::println("Size: {}, Capacity: {}", vec.size(), vec.capacity()); }
|
5.2 示例:Pool Resource
1 2 3 4 5 6 7 8
| #include <memory_resource>
std::pmr::pool_options options; options.max_blocks_per_chunk = 32; options.largest_required_block_size = 4096; auto pool = std::pmr::synchronized_pool_resource{options, std::pmr::get_default_resource()};
std::pmr::vector<std::string> strings{pool};
|
6. 性能对比
基准测试(Google Benchmark,开启 -O 优化):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| #include <benchmark/benchmark.h> #include <vector> #include <memory_resource> #include <string>
static void BM_DefaultAllocator(benchmark::State& state) { for (auto _ : state) { std::vector<std::string> vec; for (int i = 0; i < state.range(0); ++i) { vec.emplace_back("test string " + std::to_string(i)); } benchmark::DoNotOptimize(vec.data()); } }
static void BM_PMRMonotonic(benchmark::State& state) { std::byte buffer[2 * 1024 * 1024]; std::pmr::monotonic_buffer_resource resource(buffer, sizeof(buffer)); for (auto _ : state) { std::pmr::vector<std::pmr::string> vec(&resource); for (int i = 0; i < state.range(0); ++i) { vec.emplace_back("test string " + std::to_string(i)); } benchmark::DoNotOptimize(vec.data()); resource.release(); } }
static void BM_PMRPool(benchmark::State& state) { std::pmr::synchronized_pool_resource pool; for (auto _ : state) { std::pmr::vector<std::pmr::string> vec(&pool); for (int i = 0; i < state.range(0); ++i) { vec.emplace_back("test string " + std::to_string(i)); } benchmark::DoNotOptimize(vec.data()); } }
BENCHMARK(BM_DefaultAllocator)->Range(100, 10000); BENCHMARK(BM_PMRMonotonic)->Range(100, 10000); BENCHMARK(BM_PMRPool)->Range(100, 10000);
|
无 jemalloc (glibc 默认):

使用 jemalloc + ‘-O’:
这是使用 jemalloc 编译选项后的运行结果

jemalloc vs glibc 区别:
- 默认allocator 提速明显:jemalloc 优化
new/delete,单线程也快。 - PMR 相对落后:多态/池开销在 jemalloc 下更突出 (Monotonic 慢10-20%, Pool 慢20-110%)。
- 洞察:jemalloc 强化默认,PMR 需特定场景 (arena复用、多态切换)。
- 建议:生产用 LD_PRELOAD libjemalloc,默认allocator + jemalloc = 简单高效。
这是使用 jemalloc 加 ‘-O’ 编译选项后的运行结果

典型结果(相对性能):
std::allocator: 基准(1.0x)monotonic_buffer_resource: 小数据量(100-512)基本相当或略慢(1.03x),中等数据量(4096)约 1.6x 更快,大数据量(10000)约 2.2x 更快synchronized_pool_resource: 小数据量(100-512)基本相当或略慢(1.16x),中等数据量(4096)基本相当(1.09x),大数据量(10000)约 1.4x 更慢
关键洞察:
- 测量优先:不要假设 PMR 一定更快,必须在实际场景中测试。
- 场景依赖:PMR 的优势取决于数据量、生命周期和分配模式。
- 简单第一:如果默认 allocator 满足需求,不要为了"优化"而引入 PMR。
- 实际需求:PMR 的主要优势是灵活性和多态性,而不是绝对性能。
7. 与智能指针配合
7.1 std::allocate_shared
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <memory> #include <memory_resource>
struct Widget { int data; Widget(int d) : data(d) {} };
void exampleAllocateShared() { std::pmr::monotonic_buffer_resource resource; std::pmr::polymorphic_allocator<Widget> alloc{&resource}; auto ptr = std::allocate_shared<Widget>(alloc, 42); std::println("Widget data: {}", ptr->data); }
|
7.2 std::make_obj_using_allocator
C++20 引入 std::make_obj_using_allocator,简化 allocator 的使用。
8. 最佳实践
- 默认就好:99% 场景用
std::allocator。别为了"优化"引入复杂。 - PMR 入门:从小 buffer 开始测试。
monotonic 零成本抽象。 - 测量优先:用基准测试验证优化效果,别凭感觉。
- 简单第一:能用 PMR 就别自己写 allocator。
- 线程安全:多线程用
synchronized_pool_resource,单线程用 unsynchronized_pool_resource。
9. C++20/23 新特性
9.1 C++20 增强
std::pmr::polymorphic_allocator 支持 std::construct_at 和 std::destroy_at。- 更好的异常安全保证。
- 改进的
allocate_bytes 和 deallocate_bytes。 - C++23 标准中,allocator 接口已简化,不再要求实现
construct 和 destroy 成员函数。
9.2 C++23 增强
std::print 和 std::println 支持 PMR 容器的格式化输出。std::pmr::get_default_resource() 改进,线程安全的默认资源管理。- 扩展了 PMR 容器支持:
deque、forward_list、list、map、set、unordered_map 等。
10. 陷阱与调试
- 相等性:Stateful allocator 的不同实例必须能够通过
operator== 进行比较,否则容器的行为是未定义的。 - 异常安全:
allocate 抛 std::bad_alloc。 - 对齐:C++17 起,
allocate 保证 over-aligned。 - 调试:用 AddressSanitizer
clang++ -fsanitize=address。
10.1 常见错误
错误 1:Stateful allocator 拷贝问题
1 2 3 4
| std::vector<int, PoolAllocator<int>> vec1{PoolAllocator<int>(100)}; std::vector<int, PoolAllocator<int>> vec2{PoolAllocator<int>(200)}; vec1 = vec2;
|
正确做法:确保 allocator 相等或使用 propagation traits。
1 2 3 4 5 6
| template <typename T> class PoolAllocator { public: using propagate_on_container_copy_assignment = std::true_type; };
|
错误 2:忘记实现 operator==
1 2 3 4 5
| template <typename T> class MyAllocator { };
|
正确做法:实现 operator== 或设置 is_always_equal。
1 2 3 4 5 6 7
| template <typename T> class MyAllocator { public: using is_always_equal = std::true_type; bool operator==(const MyAllocator&) const noexcept { return true; } };
|
错误 3:对齐问题
1 2 3 4 5 6 7
| template <typename T> class BadAllocator { T* allocate(std::size_t n) { return static_cast<T*>(::operator new(n * sizeof(T))); } };
|
正确做法:使用 aligned new。
1 2 3 4 5 6
| template <typename T> class GoodAllocator { T* allocate(std::size_t n) { return static_cast<T*>(::operator new(n * sizeof(T), std::align_val_t{alignof(T)})); } };
|
11. 结论
Allocator 从简单 std::allocator 到 PMR,演进巨大。但记住:实用主义。解决真问题,别追虚幻性能。
核心要点:
- 默认就好:99% 场景用
std::allocator。 - PMR 是王道:需要优化时,优先用 PMR,别自己写 allocator。
- 测量优先:用基准测试验证优化效果。
- 简单第一:过度设计 allocator 往往适得其反。
- 向后兼容:Never break userspace,确保 allocator 行为一致。