簡體   English   中英

使用分配器的 new 和 delete 的等價物是什么?

[英]What's the equivalent of new and delete using allocators?

C++ 分配器(由std::vector使用)很難。 我知道他們改變了很多以允許有狀態的分配器和 PMR,導致一些笨拙。 我的核心問題是:如果分配器旨在替換newdelete ,為什么它們只提供 API 像mallocfree 我理解為什么std::vector需要一個malloc接口,因為它需要在不調用構造函數的情況下分配緩沖區,但總的來說,我們似乎缺少這些函數:

#include <cassert>
#include <iostream>
#include <memory>

//! Rebind alloc to type T
template <typename T, typename Alloc> 
auto rebound_allocator(const Alloc& alloc) {
    return typename std::allocator_traits<Alloc>::template rebind_alloc<T>{alloc};
}

//! Like operator delete but for a single T allocated by rebound_allocator<T>(alloc).
template <typename T, typename Alloc>
void allocator_delete(const Alloc& alloc, T* ptr) {
    assert(ptr);
    auto a = rebound_allocator<T>(alloc);
    using traits_t = std::allocator_traits<decltype(a)>;
    // Should we try/catch around destroy and always deallocate?
    traits_t::destroy(a, ptr);
    traits_t::deallocate(a, ptr, 1);
}

//! Returned memory must be freed with, e.g., allocator_delete(alloc, ptr).
template <typename T, typename Alloc, typename... Args>
[[nodiscard]] T* allocator_new(const Alloc& alloc, Args&&... args) {
    auto a = rebound_allocator<T>(alloc);
    using traits_t = std::allocator_traits<decltype(a)>;
    auto deallocate = [&a](T* ptr) { traits_t::deallocate(a, ptr, 1); };
    // Hold in a unique_ptr to deallocate if construction throws.
    auto buf = std::unique_ptr<T, decltype(deallocate)>(traits_t::allocate(a, 1), deallocate);
    traits_t::construct(a, buf.get(), std::forward<Args>(args)...);
    return buf.release();
}

//! Like make_unique. Beware: The allocator is is referenced by the deleter!
template <typename T, typename Alloc, typename... Args>
[[nodiscard]] auto allocator_make_unique(const Alloc& alloc, Args&&... args) {
    auto dtor = [&alloc](T* ptr) { allocator_delete<T>(alloc, ptr); };
    return std::unique_ptr<T, decltype(dtor)>(allocator_new<T>(alloc, std::forward<Args>(args)...),
                                              dtor);
}

struct S {
    float x;
    S(float x) : x(x) { std::cout << "S::S()" << std::endl; }
    ~S() { std::cout << "S::~S()" << std::endl; }
};

int main() {
    std::allocator<int> alloc;

    auto ptr = allocator_make_unique<S>(alloc, 42.5f);
    assert(ptr);
    std::cout << ptr->x << std::endl;
}

Output:

S::S()
42.5
S::~S()

https://godbolt.org/z/sheec6br3

我錯過了什么嗎? 這是使用分配器實現本質上newdeletemake_unique的正確方法嗎? 如果是這樣,這真的不是標准庫提供的嗎?

編輯:我認為(但不確定?)如果T是分配器感知的, traits_t::construct(a, ptr, n)將自己傳播到創建的 object 中?

編輯:這是一個清理后的版本: https://godbolt.org/z/47Tdzf4W7

編輯:原始版本: https://godbolt.org/z/dGW7hzdc1

我的核心問題是:如果分配器旨在替換 new 和 delete,為什么它們只提供 API 像 malloc 和免費的?

分配器的API之所以如此,是因為分配器的一個重點是memory分配和object創建必須分開。 例如,這是實現std::vector等容器所必需的。 分配器是運算符 new / delete 的泛化,而不是 new 表達式的泛化。

這真的不是標准庫提供的嗎?

不,這些函數不是由標准庫提供的。

這是使用分配器實現本質上 new 和 delete 和 make_unique 的正確方法嗎?

 auto dtor = [&alloc](T* ptr) { destruct_and_deallocate(alloc, ptr); }; ^

通過引用將分配器捕獲到刪除器中似乎很糟糕。 為了安全起見,應該復制它。

其他建議:

  • std::allocator的默認情況下,我們希望避免支付刪除器的開銷。 考慮在使用std::allocator時添加一個委托給std::make_unique的特化。

  • 您可以通過使用帶有僅解除分配的刪除器的中間唯一指針來避免 try-catch:

     T* ptr = traits_t::allocate(rebound_alloc, 1); auto dealloc = [&](T* ptr) { traits_t::deallocate(rebound_alloc, ptr, 1); }; std::unique_ptr<T, decltype(dealloc)> storage(ptr, dealloc); traits_t::construct(rebound_alloc, storage.get(), std::forward<Args>(args)...); auto dtor = [alloc](T* ptr) { destruct_and_deallocate(alloc, ptr); }; return std::unique_ptr<T, decltype(dtor)>(storage.release(), dtor);

編輯:我認為(但不確定?)如果 T 是分配器感知的,那么 traits_t::construct(a, ptr, n) 將自己傳播到創建的 object 中?

不,對象不知道創建它們或分配其 memory 的分配器。 “分配器感知”容器只是通用模板,允許用戶提供自定義分配器,並避免通過其他方式分配 memory。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM