繁体   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