1. 概述
std::pmr是 C++17 标准库中<memory_resource>头文件提供的一套内存管理框架。它通过类型擦除(Type Erasure)技术,将容器的内存分配策略从编译期绑定转变为运行期动态配置,从而解决了传统 C++ 容器在分配器(Allocator)使用上的复杂性和性能局限。
2. 核心动机与背景
在传统的 C++ 中,std::vector<T>的完整类型是std::vector<T, Allocator<T>>。
- 问题 1:编译期耦合:如果你想在同一个容器中使用不同的内存分配策略,你必须在编译期更改模板参数,这导致类型不匹配,无法进行赋值或存入同一个容器。
- 问题 2:分配器传染:使用不同分配器的容器类型不同,导致接口设计困难。
- 问题 3:性能瓶颈:频繁的
new/delete系统调用在处理大量微小对象时性能开销巨大。
std::pmr的出现正是为了实现“分配器解耦”,使容器在不改变类型的情况下,能够在运行时切换内存资源。
3. 核心组件
std::pmr围绕三个核心概念构建:
A.std::pmr::memory_resource
这是一个抽象基类,定义了内存分配的接口:
do_allocate(size_t bytes, size_t alignment): 分配内存。do_deallocate(void* p, size_t bytes, size_t alignment): 释放内存。do_is_equal(): 判断两个资源是否相等。
B.std::pmr::polymorphic_allocator<T>
它是所有std::pmr容器默认使用的分配器。它在内部持有指向memory_resource的指针。当你调用容器的allocate时,它会调用底层memory_resource的虚函数。
C. 类型别名(PMR 容器)
标准库为常见容器提供了std::pmr别名,例如:
std::pmr::vector<T>等同于std::vector<T, std::pmr::polymorphic_allocator<T>>std::pmr::string等同于std::basic_string<char, std::char_traits<char>, std::pmr::polymorphic_allocator<char>>
4. 常见的内存资源实现
std::pmr提供了几种预定义的内存资源,用于应对不同的性能场景:
| 资源名称 | 用途描述 |
|---|---|
new_delete_resource() | 简单包装::operator new和::operator delete。 |
null_memory_resource() | 永远抛出std::bad_alloc,用于禁用分配。 |
synchronized_pool_resource | 线程安全的内存池,适合频繁分配大量小对象。 |
unsynchronized_pool_resource | 非线程安全的内存池,性能比上面更快。 |
monotonic_buffer_resource | 单调递增缓冲区,只增不减,速度极快,适合临时生命周期的对象。 |
5. 代码示例
下面的代码展示了如何使用monotonic_buffer_resource,这是std::pmr最常用的场景之一:在栈上分配缓冲区,当栈对象销毁时,所有分配的内存一次性释放,无需逐个delete。
#include<memory_resource>#include<vector>#include<iostream>intmain(){// 1. 在栈上创建一个 1024 字节的缓冲区charbuffer[1024];std::pmr::monotonic_buffer_resource pool{buffer,sizeof(buffer)};// 2. 使用该资源创建一个 pmr::vector// 注意:它的类型是 pmr::vector,不需要模板参数传递分配器类型std::pmr::vector<int>vec{&pool};vec.push_back(10);vec.push_back(20);for(inti:vec){std::cout<<i<<" ";}// 无需手动释放,当 pool 超出作用域时,内存自动回收return0;}6. 总结与建议
优势
- 运行时灵活性:可以在运行时决定内存池的大小和策略。
- 性能优化:通过使用
monotonic_buffer_resource或pool_resource,可以显著减少堆分配(Heap Allocation)带来的性能抖动。 - 接口统一:通过类型擦除,不同分配策略的容器可以拥有相同的类型(例如
std::pmr::vector<int>),便于作为参数传递。
使用建议
- 性能敏感型代码:在处理大量短生命周期对象(如解析 JSON、处理数据帧)时,优先考虑
monotonic_buffer_resource。 - 避免虚函数开销:虽然
pmr有虚函数调用的微小开销,但在绝大多数业务场景下,相比于系统内存分配的开销,这些开销是可以忽略不计的。 - 兼容性:确保你的编译器支持 C++17 及以上标准。
本报告旨在概述std::pmr的基本架构与应用。如需进一步深入研究其在多线程环境下的内存对齐或自定义资源实现,建议参考 C++ 标准库实现源码。