首页 > 解决方案 > 为 STL 容器预分配内存的自定义分配器

问题描述

我想使用 astd::vector从预分配的缓冲区中为其元素分配内存。所以,我想提供一个T* buffer大小nstd::vector.

我想我可以简单地写一个类似std::span的类,它也提供了一个push_back方法;这正是我所需要的。但是,我偶然发现了这篇文章中的代码(见下文),它似乎用自定义分配器解决了这个问题。

没有人对此发表评论,但是std::vector<int, PreAllocator<int>> my_vec(PreAllocator<int>(&my_arr[0], 100));帖子中提供的示例不是以未定义的行为结束吗?我已经使用 Visual Studio 2019 实现运行代码,并且至少此实现重新绑定提供的分配器以分配 type 的元素struct std::_Container_proxy。现在这应该是一个大问题,因为您只提供了存储 100int的内存。我错过了什么吗?


template <typename T>
class PreAllocator
{
private:
    T* memory_ptr;
    std::size_t memory_size;

public:
    typedef std::size_t     size_type;
    typedef T* pointer;
    typedef T               value_type;

    PreAllocator(T* memory_ptr, std::size_t memory_size) : memory_ptr(memory_ptr), memory_size(memory_size) {}

    PreAllocator(const PreAllocator& other) throw() : memory_ptr(other.memory_ptr), memory_size(other.memory_size) {};

    template<typename U>
    PreAllocator(const PreAllocator<U>& other) throw() : memory_ptr(other.memory_ptr), memory_size(other.memory_size) {};

    template<typename U>
    PreAllocator& operator = (const PreAllocator<U>& other) { return *this; }
    PreAllocator<T>& operator = (const PreAllocator& other) { return *this; }
    ~PreAllocator() {}

    pointer allocate(size_type n, const void* hint = 0) { return memory_ptr; }
    void deallocate(T* ptr, size_type n) {}

    size_type max_size() const { return memory_size; }
};

int main()
{
    int my_arr[100] = { 0 };
    std::vector<int, PreAllocator<int>> my_vec(0, PreAllocator<int>(&my_arr[0], 100));
}

标签: c++stlcontainersc++17allocator

解决方案


vector该标准对使用提供的分配器分配的对象的类型没有要求。对元素的存储提出了要求(存储必须是连续的),但实现可以自由地对其他类型的对象进行额外分配,包括使用分配器分配原始存储以放置两个内部数据的情况容器和元素。这一点与基于节点的分配器特别相关,例如listor map,但它也适用于vector.

此外,作为用户请求的结果,该实现可以自由地执行多个分配请求。例如,两次调用push_back可能会导致两次分配请求。这意味着分配器必须跟踪先前分配的存储并从未分配的存储中执行新的分配。否则,容器的内部结构或先前插入的元素可能会损坏。

从这个意义上说,PreAllocator问题中指定的模板确实存在多个问题。最重要的是,它不跟踪分配的内存,并且总是返回指向存储开头的指针allocate。这几乎肯定会导致问题,除非用户很幸运地使用了一个特定的实现,vector该实现除了为其元素分配存储之外不分配任何东西,并且用户非常小心他在vector.

接下来,分配器不会检测到存储耗尽。这可能导致超出范围的错误条件。

最后,分配器不能确保分配的存储正确对齐。底层缓冲区仅与 对齐alignof(int),如果容器分配其具有更高对齐要求的内部结构(例如,如果结构包含指针,并且指针大于 ),这可能还不够int

设计分配器时的一般建议是根据字节的原始存储来实现它们。该存储可用于创建不同类型、大小和对齐要求的对象,这些对象可以通过分配器的不同副本进行分配(在重新绑定到其他类型之后)。从这个意义上说,你传递给容器的分配器类型只是一个句柄,它可以被容器在它认为合适的时候反弹和复制。


推荐阅读