[英]What's the equivalent of new and delete using allocators?
C++ 分配器(由std::vector
使用)很難。 我知道他們改變了很多以允許有狀態的分配器和 PMR,導致一些笨拙。 我的核心問題是:如果分配器旨在替換new
和delete
,為什么它們只提供 API 像malloc
和free
? 我理解為什么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
我錯過了什么嗎? 這是使用分配器實現本質上new
和delete
和make_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.