【问题标题】:How to custom deallocate an object from a base class pointer?如何从基类指针自定义释放对象?
【发布时间】:2023-01-01 22:31:08
【问题描述】:

我有一个存储在std::vector<std::unique_ptr<Base>> 中的类层次结构。经常从这个向量中添加和删除,所以我想尝试自定义内存分配以避免对 newdelete 的所有调用。我只想使用 STL 工具,所以我正在尝试 std::pmr::unsynchronized_pool_resource 进行分配,然后将自定义删除器添加到 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;
};

但随后每种类型的 unique_ptrs 都不兼容,向量将无法保存它们。

然后我尝试通过基类解除分配:

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

但这显然是个坏主意,因为释放的内存与分配的内存具有不同的大小和对齐方式。

那么,如何通过基指针解除分配而不在运行时存储大小和对齐方式? delete 似乎可以管理,所以这里似乎也应该可以。

【问题讨论】:

  • 大多数分配器存储额外的信息(在返回的内存或其他地方)。
  • new 还存储额外的信息,你看不到,delete 使用。编译器可能会提供一些帮助来减少开销,但这既不是这里也不是那里。

标签: c++ std allocation


【解决方案1】:

据我所知,大多数分配器都会直接在返回给您的指针旁边分配额外的空间来存储释放所需的任何数据。我们可以对aPtr blob 做同样的事情:

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

// 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;
};

// Extra information needed for deallocation
struct Header {
    std::size_t s;
    std::size_t a;
    std::pmr::memory_resource* res;
};

// Deleter is now just a free function
void deleter(Base* a) {
    // First delete the object itself.
    a->~Base();
    // Obtain the header
    auto* ptr = reinterpret_cast<unsigned char*>(a);
    Header* header = reinterpret_cast<Header*>(ptr - sizeof(Header));
    // Deallocate the allocated blob.
    header->res->deallocate(ptr, header->s, header->a);
};

// Use the new custom function.
template <typename T>
using Ptr = std::unique_ptr<T, decltype(&deleter)>;

template <typename T, typename... Args>
Ptr<T> newT(std::pmr::memory_resource& m, Args... args) {
    // Let the compiler calculate the correct way how to store `T` and `H`
    // together.
    struct Storage {
        Header header;
        T type;
    };
    Header h = {sizeof(Storage), alignof(Storage)};

    auto aPtr = m.allocate(h.s, h.a);
    // Use dummy header.
    Storage* storage = new (aPtr) Storage{h, T(args...)};
    static_assert(sizeof(Storage) == (sizeof(Header) + sizeof(T)),
                  "No padding bytes allowed in Storage.");

    return Ptr<T>(&storage->type, deleter);
}

我们将释放所需的所有信息存储在Header 结构中。

在单个 blob 中同时分配 T 和标头并不像看起来那么简单。我们至少需要 sizeof(T)+sizeof(Header) 字节,但也必须尊重 alignof(T)。所以我们让编译器通过Storage 来解决它。

这样我们就可以正确分配T,并返回一个指向&amp;storage-&gt;type的指针给用户。现在的问题是,在headertype之间,Storage中可能存在一些-deleter-未知数量的填充,因此deleter函数将无法仅从@恢复&amp;storage-&gt;header 987654336@指针。 我对此有两个建议:

  • 只需将填充量断言为 0。
  • 在已知位置手动写入标头,尽管我不能保证 100% 安全。

限制为已知填充

尽管 Storage 中的额外填充不太可能,因为 Header 在正常的 64 位系统上对齐到 8 个字节,这对于所有 Ts 通常应该足够了,但在 C++ 中没有这样的对齐保证。 vtable pointer 使恕我直言和所有编译器都提供一些用户对对齐的控制的事实更加难以保证,特别是增加它例如矢量指令,也无济于事。但是如果你想保存额外的memcpy并直接使用Storage::header,只需使用static_assert,如果出现任何“奇怪”的类型,代码将不会编译并保持安全。

手动写标题:

所以我们直接忽略storage-&gt;header成员,直接在type之前自己写header。这需要使用memcopy,因为我们不能仅仅将-new放在那里,因为可能存在alignof(Header)不匹配。在deleter 本身也是一样,因为在ptr-sizeof(Header) 没有Header 对象,简单的reinterpret_cast&lt;Header*&gt;(ptr-sizeof(header)) 会打破严格的别名规则。

// Extra information needed for deallocation
struct Header {
    std::size_t s;
    std::size_t a;
    std::pmr::memory_resource* res;
};

// Deleter is now just a free function
void deleter(Base* a) {
    // First delete the object itself.
    a->~Base();
    // Obtain the header
    auto* ptr = reinterpret_cast<unsigned char*>(a);
    Header header;
    std::memcpy(&header,ptr-sizeof(Header),sizeof(Header));
    // Deallocate the allocated blob.
    header.res->deallocate(ptr, header.s, header.a);
};

// Use the new custom function.
template <typename T>
using Ptr = std::unique_ptr<T, decltype(&deleter)>;

template <typename T, typename... Args>
Ptr<T> newT(std::pmr::memory_resource& m, Args... args) {
    // Let the compiler calculate the correct way how to store `T` and `H` together.
    struct Storage {
        Header header;
        //Padding???
        T type;
    };
    Header h = {sizeof(Storage), alignof(Storage)};

    auto aPtr = m.allocate(h.s,h.a);
    // Use dummy header.
    Storage* storage = new (aPtr) Storage{{0, 0}, T(args...)};

    // Write our own header at the known -sizeof(Header) offset.
    auto* ptr = reinterpret_cast<unsigned char*>(storage);
    std::memcpy(ptr-sizeof(Header),&h,sizeof(Header));

    return Ptr<T>(&storage->type, deleter);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-28
    • 1970-01-01
    • 2017-12-23
    • 2010-12-04
    相关资源
    最近更新 更多