[英]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.