[英]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.