C++如何使用std::pmr实现高效灵活的内存资源管理?

来源:DB2教程作者:小团团头衔:草根站长
导读:本期聚焦于小伙伴创作的《C++如何使用std::pmr实现高效灵活的内存资源管理?》,敬请观看详情。传统C++手动管理内存容易引发碎片与泄漏,而标准库自C++17起提供的多态内存资源让分配策略可运行时替换。std::pmr通过memory_resource基类与polymorphic_allocator配合,将容器与具体分配器解耦。借助monotonic_buffer_resource可在局部作用域复用缓冲区,大幅降低频繁申请释放的系统调用开销。unsynchronized_pool_resource则适合多线程独立池化。理解resource生命周期与容器拷贝行为,能避免悬空引用与意外共享,是落地pmr的关键。

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

C++如何使用std::pmr实现高效灵活的内存资源管理?

一、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_resourcestd::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

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。