712 字
4 分钟
std::pmr 的分配器边界

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 这样的统一类型。但它把两个责任还给了你:资源的生命周期,以及跨资源的赋值语义。用之前先把这两件事在代码结构上安排清楚,再谈性能。

std::pmr 的分配器边界
/posts/cpp-pmr/
作者
fantasy-peak
发布于
2026-10-22
许可协议
CC BY-NC-SA 4.0