在C++17标准之前,我们习惯用new、delete或者自定义分配器来控制内存,但容器与分配器深度耦合,切换策略往往要重写模板参数。std::pmr(Polymorphic Memory Resources)引入了一套基于运行时多态的内存资源体系,让同一个容器类型可以在不同分配策略之间灵活切换,而不必在编译期固定分配器。

一、std::pmr核心概念
std::pmr的核心是一个抽象基类std::pmr::memory_resource,它定义了allocate、deallocate和is_equal三个接口。任何具体资源(如系统内存、池、栈缓冲区)都继承自它。与传统的std::allocator不同,pmr分配器std::pmr::polymorphic_allocator内部只保存一个指向memory_resource的指针,因此同一个allocator类型可以表现出完全不同的行为。
这种设计带来了两个好处:一是容器类型不再需要把分配器作为模板参数写死,例如可以用std::pmr::vector<int>而不是std::vector<int, MyAlloc>;二是资源对象可以在运行时更换,只要保证容器使用的资源生命周期长于容器本身。下面是一段最基础的用法示例,展示如何用一个简单的缓冲区资源来驱动容器。
#include <memory_resource>
#include <vector>
#include <iostream>
int main() {
// 准备一块栈上缓冲区
char buffer[1024];
std::pmr::monotonic_buffer_resource resource(buffer, sizeof(buffer));
// 使用pmr vector,分配内存都走resource
std::pmr::vector<int> vec(&resource);
for (int i = 0; i < 10; ++i) {
vec.push_back(i);
}
for (int v : vec) {
std::cout << v << " ";
}
return 0;
}
上面的代码里,vector的所有内存申请都落在buffer所代表的monotonic_buffer_resource中,没有调用系统堆。需要注意,monotonic_buffer_resource只分配不回收,适用于生命周期短、分配密集的场景。
二、常用的标准内存资源
C++标准提供了几种开箱即用的资源。首先是std::pmr::new_delete_resource(),它底层就是调用全局new和delete,通常作为默认资源。其次是std::pmr::null_memory_resource(),任何分配请求都会抛bad_alloc,常用于测试或禁止某些路径分配内存。
更实用的是std::pmr::unsynchronized_pool_resource和std::pmr::synchronized_pool_resource。前者不带锁,适合单线程内复用小块内存;后者内部加锁,可安全用于多线程。它们会把大块内存从上游资源申请下来,再切成小池分给容器,显著降低系统调用次数。下面的例子演示了池资源如何减少碎片。
#include <memory_resource>
#include <vector>
#include <string>
int main() {
std::pmr::unsynchronized_pool_resource pool;
std::pmr::string s1(&pool);
s1 = "hello pmr";
std::pmr::vector<std::pmr::string> vec(&pool);
vec.emplace_back("a");
vec.emplace_back("b");
// 所有string和vector节点都从pool里分配
return 0;
}
使用池资源时要小心:当pool销毁后,从中分配出去的内存全部失效。因此绝不能让容器或字符串在pool释放后继续被访问,否则会产生悬空指针。
三、资源生命周期与容器拷贝
pmr容器保存的是资源指针,而不是资源副本。当用一个pmr容器拷贝或移动构造另一个时,新容器会指向同一个资源。这意味着如果资源是栈对象,而容器被返回到上层作用域,就会引用已销毁的资源。解决方法是把资源放在堆上,或用std::pmr::get_default_resource()管理全局默认资源。
我们也可以通过std::pmr::set_default_resource()替换进程级默认资源,这样不显式传资源的pmr容器会自动使用它。但该函数非线程安全,一般只在启动阶段调用。下面的代码展示了默认资源的设置与影响范围。
#include <memory_resource>
#include <vector>
int main() {
std::pmr::monotonic_buffer_resource local_res;
auto old = std::pmr::set_default_resource(&local_res);
// 不传参数,使用当前默认资源
std::pmr::vector<int> v;
v.resize(5);
std::pmr::set_default_resource(old);
return 0;
}
拷贝容器时若想切换资源,可以借助allocator参数构造。例如用std::pmr::vector<int>(other_vec.begin(), other_vec.end(), &new_res)显式指定新资源,从而避免资源共享带来的生命周期问题。
四、自定义内存资源
如果标准资源不能满足需求,可以从std::pmr::memory_resource派生,重写do_allocate、do_deallocate和do_is_equal。比如做一个统计分配字节数的资源,方便定位内存热点。自定义资源要注意线程安全:如果会被多线程共享,内部必须自己加锁。
下面给出一个极简的计数资源示例,它在每次分配时累加大小,用于调试观察。
#include <memory_resource>
#include <cstddef>
#include <mutex>
class counting_resource : public std::pmr::memory_resource {
public:
std::size_t total = 0;
protected:
void* do_allocate(std::size_t bytes, std::size_t align) override {
total += bytes;
return ::operator new(bytes);
}
void do_deallocate(void* p, std::size_t bytes, std::size_t align) override {
total -= bytes;
::operator delete(p);
}
bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override {
return this == &other;
}
};
int main() {
counting_resource res;
std::pmr::vector<int> v(&res);
v.resize(100);
// res.total 可查看分配总量
return 0;
}
这个例子虽然简单,但体现了pmr的扩展能力:只要遵循接口,任何分配策略都能无缝接入标准容器,无需修改容器定义。
五、实践建议
在性能敏感的服务中,推荐把短期批处理所用的vector、string统一绑定到monotonic_buffer_resource,处理完即整体释放。对于长生命周期且分配频繁的组件,使用unsynchronized_pool_resource并绑定到该组件自有资源上,可以避免全局堆竞争。
同时,团队应当明确资源归属:谁创建资源、谁负责释放、哪些容器引用了它。在接口边界传递pmr容器时,最好附带资源引用或约定使用默认资源,减少误用。只要理清这些关系,std::pmr就能在几乎不改动业务代码的前提下,带来可观的内存与性能收益。
std::pmrPolymorphic_Memory_ResourcesC++内存管理修改时间:2026-08-11 14:27:35