[英]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;
};
這個實現有兩個問題:
分配器必須是可復制分配的,上面的類不能復制。 特別是,一個塊將包含指向其他塊的指針。 因此,如果重新分配 m_storage,鏈接將被破壞。 我必須在這里使用 std::shared_ptr 嗎? 還是應該復制簡單地創建一個具有源容量的分配器,而不關心源的當前狀態?
當使用自定義分配器實例化容器時,它會使用分配器創建,分配器將為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.