繁体   English   中英

为 std::multiset 预分配存储

[英]Preallocate storage for std::multiset

我想为 std::multiset 使用预分配的存储。 我事先知道它的大小上限,但仅限于运行时。 因此,我编写了一个像这样的堆栈分配器。 如果碰巧请求了 1 个以上的元素,它会使用标准分配器作为后备:

template<class T>
class PreallocStackAllocator
{
public:
    using Chunk        = std::aligned_storage_t<sizeof(T), alignof(T)>;
    using ChunkPointer = Chunk*;
    static_assert(sizeof(Chunk) >= sizeof(T));

    using value_type = T;

    explicit PreallocStackAllocator(size_t capacity)
        : m_freelist{std::make_unique<ChunkPointer[]>(capacity)}
        , m_freelist_end{capacity}
        , m_storage{std::make_unique<Chunk[]>(capacity)}
        , m_capacity{capacity}
    {
        std::generate_n(m_freelist.get(),
                        capacity,
                        [base_address = m_storage.get(), k = static_cast<size_t>(0)]() mutable {
                            auto ret = base_address + k;
                            ++k;
                        return ret;
                    });
    }

    [[nodiscard]] T* allocate(size_t n)
    {
        if(n != 1) [[unlikely]]
        {
            return m_default_allocator.allocate(n);
        }

        assert(m_freelist_end != 0);
        --m_freelist_end;
        return reinterpret_cast<T*>(m_freelist[m_freelist_end]);
    }

    void deallocate(T* ptr, size_t n)
    {
        if(n != 1) [[unlikely]]
        {
            return m_default_allocator.deallocate(ptr, n);
        }

        if(ptr == nullptr) { return; }

        m_freelist[m_freelist_end] = reinterpret_cast<Chunk*>(ptr);
        ++m_freelist_end;
    }

    std::span<size_t const> freelist() const
    {
        return std::span{m_freelist.get(), m_freelist_end};
    }

    size_t capacity() const { return m_capacity; }

private:
    std::unique_ptr<ChunkPointer[]> m_freelist;
    size_t m_freelist_end;
    std::unique_ptr<Chunk[]> m_storage;
    size_t m_capacity;
    [[no_unique_address]] std::allocator<T> m_default_allocator;
};

这个实现有两个问题:

  1. 分配器必须是可复制分配的,上面的类不能复制。 特别是,一个块将包含指向其他块的指针。 因此,如果重新分配 m_storage,链接将被破坏。 我必须在这里使用 std::shared_ptr 吗? 还是应该复制简单地创建一个具有源容量的分配器,而不关心源的当前状态?

  2. 当使用自定义分配器实例化容器时,它会使用分配器创建,分配器将为capacity T:s 分配空间,并使用capacity指针创建空闲列表。 然而,实际上,我们永远不会分配 T:s,而是分配 std::_Rb_tree_node。 据我了解,这个映射是通过 rebind 元函数完成的。 无论如何,我们现在也将为真正的内容分配空间,但是我们现在在创建原始分配器时浪费了空间。 如何解决这个问题?

事实证明,我可以使用代理分配器,其目的是转发要分配的元素数量:

    namespace prealloc_multiset_detail
    {
        template<class T>
        class Allocator: public PreallocStackAllocator<T>
        {
        public:
            template<class CapacityHolder>
            requires requires
            {
                typename CapacityHolder::IsCapacityHolder;
            }
            explicit Allocator(CapacityHolder capacity)
                : PreallocStackAllocator<T>{capacity.capacity()}
            {
            }
        };

        template<class Type>
        class AllocatorProxy
        {
        public:
            using value_type = Type;

            template<class U>
            requires(!std::same_as<U, Type>) struct rebind
            {
                using other = Allocator<U>;
            };

            explicit AllocatorProxy(size_t capacity): m_capacity{capacity} {}

            size_t capacity() const { return m_capacity; }

            struct IsCapacityHolder
            {
            };

        private:
            size_t m_capacity;
        };
    }

该系列将无法复制,移动后将死亡 但是我不需要复制它,在我的情况下也不需要在移动的对象上insert

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM