一、C++17 std::pmr到底是什么?概念与定位
std::pmr 的核心概念
在C++17中,std::pmr 引入了“多态内存资源”的概念,旨在解耦内存分配与具体的分配策略。它通过 memory_resource 基类定义统一接口,使得不同的分配策略可以互换,而不改变使用端的代码。通过这种设计,开发者能够在需要时切换内存来源,从而达到更好的性能和可控的内存行为。
核心思想是把分配策略 与 分配对象的使用 解耦,利用 polymorphic_allocator 将同一份分配逻辑应用到多种容器类型。这样,程序员就可以把内存分配的实现放在资源对象里,而不是写死在容器内部。通过这种模式,内存分配策略具备可扩展性,并且便于测试与优化。
多态内存资源的接口与工作方式
memory_resource 提供统一的分配与回收入口,内部通过重载 allocate()、deallocate() 等方法实现多态行为。对开发者而言,重点并不是直接调用这些低层方法,而是通过 polymorphic_allocator 与容器协同工作,享受灵活的内存资源。
通过 get_default_resource() 可以得到全局默认资源,进一步通过 pool_resource、monotonic_buffer_resource 等实现进行组合,达到对内存生命周期与碎片控制的目标。也就是说,PMR 的强大之处在于资源组合和可替换性,而不仅仅是单一的内存分配器。
#include
#include
#include int main() {// 使用一个简单的 monotonic_buffer_resource 作为上游资源char buffer[1024];std::pmr::monotonic_buffer_resource pool{buffer, sizeof buffer};// 将分配行为绑定到该资源std::pmr::polymorphic_allocator alloc{&pool};// 使用 PMR 版本的 vectorstd::pmr::vector v{alloc};v.push_back('H');v.push_back('i');std::cout << std::string(v.begin(), v.end()) << std::endl;// 当 pool 填充完成且作用域结束时,相关内存随 pool 一并释放return 0;
}
二、从多态内存资源到内存池的原理
内存资源的接口设计
在 memory_resource 的设计中,关键点是提供一个可替换的内存获取路径,进而让 容器的分配器 可以无缝地切换到不同的资源。实现要点包括对齐、分配粒度以及错误处理的策略。通过覆盖 do_allocate/do_deallocate 等虚方法,自定义资源能够对齐特定场景,如高并发、低延迟或大对象分配。
此外,is_equal 的实现决定了资源之间的可比性,确保在某些场景下资源可以互换使用,从而避免不必要的内存拷贝与重复初始化。对于大规模系统,资源比较与组合成为提升性能的关键环节。
常见实现:monotonic_buffer_resource 与 pool_resource
monotonic_buffer_resource 是一种不具备独立回收能力的资源,它通过一个预先分配好的 Arena(内存区域)来满足分配请求,内存通常在资源销毁时统一释放。它非常适合临时性对象、短生命周期对象的高效分配,能够显著降低分配开销与碎片。
另一种常见实现是 pool_resource,它常与 upstream 资源组合,构建一个多层次的内存池。通过层级缓存,同一对象的分配和回收可以重复利用,从而降低系统调用与碎片的概率。对于多生产者场景,pool_resource 的性能优势尤为明显。

#include
#include
#include int main() {// upstream 使用全局默认资源std::pmr::pool_resource pool{std::pmr::get_default_resource()};std::pmr::polymorphic_allocator alloc{&pool};std::pmr::vector v{alloc};v.assign({'P','M','R',' ','实','践'});for(char c: v) std::cout << c;std::cout << std::endl;return 0;
}
结合使用时,monotonic_buffer_resource 可以作为快速分配的前端缓存,pool_resource 作为更长期的回收与复用机制,二者共同构成灵活的内存管理方案。对于大型工程,正确选择与组合,是降低内存抖动与提升吞吐量的关键。
三、实战应用:将 std::pmr 落地到实际项目
在容器中的用法:std::pmr::vector、std::pmr::string
将 std::pmr 族容器替代传统 STL 容器,能够让分配策略在编译期保持透明、在运行时灵活切换。通过给容器绑定一个 memory_resource,就能实现自定义分配行为,同时保持接口不变,从而减少代码侵入与改动成本。
一个常见的实践是先创建一个 std::pmr::monotonic_buffer_resource 或 pool_resource,然后用 std::pmr::polymorphic_allocator 作为容器的分配器。这样可以在需要时更换资源,而不修改容器使用逻辑。以下代码展示了基础用法:
#include
#include
#include int main() {char buf[2048];std::pmr::monotonic_buffer_resource mbr{buf, sizeof buf};std::pmr::polymorphic_allocator alloc{&mbr};std::pmr::vector data{alloc};data.insert(data.end(), {'L','o','c','a','l','P','R'});std::cout << std::string(data.begin(), data.end()) << std::endl;// 退出作用域,mbr 自动释放内存return 0;
}
自定义资源与性能考量
在需要极致控制的场景,自定义 memory_resource 是一种有效手段。通过实现自己的分配策略,可以针对特定对象类型、生命周期和并发特性进行优化。需要注意的是,对齐、碎片回收策略与并发安全性 是实现要点,也是性能瓶颈常见来源。
在实践中,应该对比基于默认资源的实现与自定义资源的性能差异,关注点包括:吞吐量、峰值内存使用、分配延迟与回收时效,以及在多线程环境中的资源竞争。通过基准测试,可以判断是否值得引入更复杂的资源层。
#include
#include
#include class MySimpleResource : public std::pmr::memory_resource {
protected:void* do_allocate(size_t bytes, size_t alignment) override {// 简单示例:调用全局 operator newvoid* p = ::operator new(bytes, std::nothrow);return p;}void do_deallocate(void* p, size_t bytes, size_t alignment) override {::operator delete(p);}bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override {return this == &other;}
};int main() {MySimpleResource res;std::pmr::polymorphic_allocator alloc{&res};std::pmr::vector v{alloc};v.assign({'S','e','l','f','-','M','R'});std::cout << std::string(v.begin(), v.end()) << std::endl;return 0;
}
通过上述用法,可以看到 std::pmr 实现了将内存分配策略内聚到资源对象中的能力,若在团队中形成规范化的分配策略,长期来看将显著提升代码的可维护性和性能可预期性。


