c++ - 使用分配器的 new 和 delete 等同于什么?

标签 c++ allocator

C++ 分配器(如 std::vector 使用的那样)很难操作。我知道他们改变了很多以允许有状态的分配器和 PMR,从而导致一些问题。我的核心问题是:如果分配器旨在替换 newdelete,为什么它们只提供像 mallocfree 这样的 API ?我理解为什么,例如,std::vector 需要一个 malloc 接口(interface),因为它需要在不调用构造函数的情况下分配一个缓冲区,但总的来说,我们似乎缺少这些功能:

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

输出:

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

https://godbolt.org/z/sheec6br3

我错过了什么吗?这是使用分配器实现 newdelete 以及 make_unique 的正确方法吗?如果是这样,这真的不是标准库提供的吗?

编辑: 我认为(但不确定?)如果 T 是分配器感知的,traits_t::construct(a, ptr, n) 会将自身传播到创建的对象中?

编辑: 这是清理后的版本:https://godbolt.org/z/47Tdzf4W7

编辑: 原版:https://godbolt.org/z/dGW7hzdc1

最佳答案

My core question is: if allocators are intended to replace new and delete, why do they only provide an API like malloc and free?

分配器的API之所以如此,是因为分配器的一个重要点是内存分配和对象创建必须分开。例如,这是实现 std::vector 等容器所必需的。分配器是 operator new/delete 的泛化,而不是 new 表达式的泛化。

is this really not provided by the standard library?

不,标准库不提供这些函数。

Is this the right way to implement essentially new and delete and make_unique using allocators?

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

通过引用将分配器捕获到删除器中似乎很糟糕。为了安全起见,应该复制它。

其他建议:

  • std::allocator 的默认情况下,我们希望避免支付删除器的开销。考虑在使用 std::allocator 时添加委托(delegate)给 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);
    

Edit: I think (but am not sure?) that if T is allocator-aware, traits_t::construct(a, ptr, n) will propagate itself into the created object?

不,对象不知道创建它们或分配内存的分配器。 “分配器感知”容器只是通用模板,允许用户提供自定义分配器,并避免通过其他方式分配内存。

关于c++ - 使用分配器的 new 和 delete 等同于什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70019488/

相关文章:

c++ - 使用数组作为映射键 : impossible even with custom allocator?

c++ - 为什么 std::vector::get_allocator() 按值返回?

c++ - 我可以在我的 VCL 客户端-服务器应用程序中使用端口 80 吗?

c++ - 在插入期间对链表进行排序

c++ - 将cv::Mat转换为std::vector的泛型函数

c++ - CMake 使用预处理器定义有条件地添加 Qt 信号

c++ - 是否有不会隐式释放内存的 STL 分配器?

c++ - 为什么我的带有数组参数的方法不能正常工作? (C++)

c++ - 如何使用 C++ 11 样式的非默认可构造分配器指定初始大小?

c++ - Apple Clang:无法使用自定义分配器编译对 vector 的 std::erase 调用