You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过基类指针自定义释放PMR分配的类层次对象?

使用std::pmr池资源管理多态unique_ptr的内存分配

我有一个类层次结构,对象存储在std::vector<std::unique_ptr<Base>>中。因为需要频繁对这个向量做增删操作,想通过自定义内存分配避免频繁调用new和delete。我只想用STL工具,所以尝试用std::pmr::unsynchronized_pool_resource做内存分配,同时给std::unique_ptr加自定义删除器。

以下是我目前的实现代码:

#include <memory_resource>
#include <vector>
#include <memory>


// dummy classes

struct Base
{
    virtual ~Base() {}
};

struct D1 : public Base
{
    D1(int i_) : i(i_) {}
    int i;
};

struct D2 : public Base
{
    D2(double d_) : d(d_) {}
    double d;
};


// custom deleter: this is what I'm concerned about

struct Deleter
{
    Deleter(std::pmr::memory_resource& m, std::size_t s, std::size_t a) :
        mr(m), size(s), align(a) {}

    void operator()(Base* a)
    { 
        a->~Base();
        mr.get().deallocate(a, size, align);
    }

    std::reference_wrapper<std::pmr::memory_resource> mr;
    std::size_t size, align;
};


template <typename T>
using Ptr = std::unique_ptr<T, Deleter>;


// replacement function for make_unique

template <typename T, typename... Args>
Ptr<T> newT(std::pmr::memory_resource& m, Args... args)
{
    auto aPtr = m.allocate(sizeof(T), alignof(T));

    return Ptr<T>(new (aPtr) T(args...), Deleter(m, sizeof(T), alignof(T)));
}

// simple construction of vector

int main()
{
    auto pool = std::pmr::unsynchronized_pool_resource();
     
    auto vec = std::vector<Ptr<Base>>();

    vec.push_back(newT<Base>(pool));
    vec.push_back(newT<D1>(pool, 2));
    vec.push_back(newT<D2>(pool, 4.0));

    return 0;
}

这段代码能编译,我觉得不会有内存泄漏(如果有误请指出!),但我对这个需要额外传入大小和对齐参数的Deleter类不太满意。

我最初尝试把它改成模板类,自动获取大小和对齐信息:

template <typename T>
struct Deleter
{
    Deleter(std::pmr::memory_resource& m) :
        mr(m) {}

    void operator()(Base* a)
    { 
        a->~Base();
        mr.get().deallocate(a, sizeof(T), alignof(T));
    }

    std::reference_wrapper<std::pmr::memory_resource> mr;
};

但这样一来,不同类型的std::unique_ptr会不兼容,没法存入同一个向量。

之后我尝试通过基类进行释放:

mr.get().deallocate(a, sizeof(Base), alignof(Base));

这显然是错的,因为释放的内存大小和对齐方式和分配时不一致。

那有没有办法在运行时不存储大小和对齐信息的情况下,通过基类指针完成释放?delete运算符能做到这一点,所以我觉得这里应该也有可行的方法。


解决方案:让基类提供内存释放接口

delete能正确释放派生类对象,本质是虚析构函数隐含了对象的大小、对齐等信息,编译器会为每个派生类生成对应的析构和释放逻辑。我们可以模仿这个思路,让基类提供虚函数,由派生类负责自身的内存释放:

  1. 修改基类,添加纯虚的destroy函数:
struct Base
{
    virtual ~Base() {}
    virtual void destroy(std::pmr::memory_resource& mr) = 0;
};
  1. 每个派生类实现destroy函数,自行处理析构和内存释放:
struct D1 : public Base
{
    D1(int i_) : i(i_) {}
    int i;

    void destroy(std::pmr::memory_resource& mr) override {
        this->~D1();
        mr.deallocate(this, sizeof(D1), alignof(D1));
    }
};

struct D2 : public Base
{
    D2(double d_) : d(d_) {}
    double d;

    void destroy(std::pmr::memory_resource& mr) override {
        this->~D2();
        mr.deallocate(this, sizeof(D2), alignof(D2));
    }
};
  1. 简化删除器,不再需要存储大小和对齐信息:
struct Deleter
{
    Deleter(std::pmr::memory_resource& m) : mr(m) {}

    void operator()(Base* a) {
        a->destroy(mr.get());
    }

    std::reference_wrapper<std::pmr::memory_resource> mr;
};
  1. 调整newT函数,无需再传递大小和对齐参数给删除器:
template <typename T, typename... Args>
Ptr<T> newT(std::pmr::memory_resource& m, Args... args)
{
    auto aPtr = m.allocate(sizeof(T), alignof(T));
    return Ptr<T>(new (aPtr) T(args...), Deleter(m));
}

修改后,删除器不再需要额外存储大小和对齐信息,所有释放逻辑由派生类自行负责,既和delete的工作原理一致,也能保证所有Ptr<Base>类型兼容,可存入同一个向量。

额外优化:用CRTP减少代码冗余

如果不想给每个派生类手动编写destroy函数,可以用CRTP(奇异递归模板模式)简化:

template <typename Derived>
struct BaseWithDestroy : Base
{
    void destroy(std::pmr::memory_resource& mr) override {
        static_cast<Derived*>(this)->~Derived();
        mr.deallocate(this, sizeof(Derived), alignof(Derived));
    }
};

// 派生类继承模板类即可
struct D1 : BaseWithDestroy<D1> {
    D1(int i_) : i(i_) {}
    int i;
};
struct D2 : BaseWithDestroy<D2> {
    D2(double d_) : d(d_) {}
    double d;
};

补充说明

你的原始代码确实不会存在内存泄漏,每个对象都正确调用了析构函数,且用分配时的大小和对齐参数调用了deallocate。


内容的提问来源于stack exchange,提问作者Liam Goodacre

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 07:25:14