std::pmr(polymorphic memory resource)解决的是 C++ 分配器的一个老问题:分配器是容器类型的一部分,vector<T, A1> 和 vector<T, A2> 是不同类型,接口之间很难传递。pmr 把分配策略塞进运行期对象,让容器类型统一成 std::pmr::vector<T>。
代价是,资源的生命周期和赋值语义变成了你需要自己盯住的东西。
资源是一个对象
#include <memory_resource>
std::pmr::monotonic_buffer_resource arena;std::pmr::vector<int> v{&arena};v.push_back(1); // 从 arena 上切一块monotonic_buffer_resource 只增不减,release() 一次性归还全部内存。它适合“一批相关对象一起创建、一起销毁”的场景,比如处理一个请求期间的所有临时对象。
它的构造函数还能接一块栈上的缓冲区:
std::array<std::byte, 4096> buf;std::pmr::monotonic_buffer_resource arena{buf.data(), buf.size()};小于 4KB 的分配不会碰堆,超过之后才向上游资源要。
边界一:资源必须活得更久
容器只保存一个 memory_resource*,不持有它。下面这段代码是悬垂的:
std::pmr::vector<int> make() { std::pmr::monotonic_buffer_resource arena; std::pmr::vector<int> v{&arena}; v.push_back(1); return v; // arena 已销毁,v 的析构会访问悬垂指针}正确做法是把 arena 放在调用方,或者用 std::pmr::get_default_resource() 这类进程级资源。嵌套资源也要注意:子资源通常持有父资源的指针,父资源必须先于子资源构造、晚于子资源销毁。
边界二:跨资源赋值不会迁移
std::pmr::monotonic_buffer_resource a, b;std::pmr::vector<int> va{&a};std::pmr::vector<int> vb{&b};va = vb; // 元素被复制进 va,但 va 仍然从 a 分配容器的分配器不会因为赋值而切换到 b。propagate_on_container_copy_assignment 对 polymorphic_allocator 默认是 false,所以 va 的后续增长仍然落在 a 上。如果你希望“赋值后跟目标资源走”,要么显式构造新容器,要么在接口上避免跨资源赋值。
反过来,移动赋值在两个容器的资源 do_is_equal 返回 true 时才能直接接管缓冲区,否则只能逐元素搬。这在热路径上可能是一次意外的分配风暴。
对齐与自定义资源
实现 memory_resource 时,do_allocate(bytes, alignment) 必须满足对齐要求。用 std::pmr::monotonic_buffer_resource 时它自己会处理;手写 arena 时如果只按 max_align_t 对齐,遇到 alignas(64) 的类型就会 UB。
class arena_resource final : public std::pmr::memory_resource { void* do_allocate(std::size_t bytes, std::size_t alignment) override; void do_deallocate(void*, std::size_t, std::size_t) override; bool do_is_equal(const memory_resource& other) const noexcept override { return this == &other; }};do_is_equal 的语义是“两个资源能否互相释放对方分配的内存”。返回 this == &other 是保守且正确的做法。
结论
pmr 的价值在于把分配策略从类型系统里拿出来,让接口只依赖 std::pmr::vector 这样的统一类型。但它把两个责任还给了你:资源的生命周期,以及跨资源的赋值语义。用之前先把这两件事在代码结构上安排清楚,再谈性能。