簡體   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