c++ - 为 STL 容器预分配内存的自定义分配器
问题描述
我想使用 astd::vector
从预分配的缓冲区中为其元素分配内存。所以,我想提供一个T* buffer
大小n
为std::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));
}
解决方案
vector
该标准对使用提供的分配器分配的对象的类型没有要求。对元素的存储提出了要求(存储必须是连续的),但实现可以自由地对其他类型的对象进行额外分配,包括使用分配器分配原始存储以放置两个内部数据的情况容器和元素。这一点与基于节点的分配器特别相关,例如list
or map
,但它也适用于vector
.
此外,作为用户请求的结果,该实现可以自由地执行多个分配请求。例如,两次调用push_back
可能会导致两次分配请求。这意味着分配器必须跟踪先前分配的存储并从未分配的存储中执行新的分配。否则,容器的内部结构或先前插入的元素可能会损坏。
从这个意义上说,PreAllocator
问题中指定的模板确实存在多个问题。最重要的是,它不跟踪分配的内存,并且总是返回指向存储开头的指针allocate
。这几乎肯定会导致问题,除非用户很幸运地使用了一个特定的实现,vector
该实现除了为其元素分配存储之外不分配任何东西,并且用户非常小心他在vector
.
接下来,分配器不会检测到存储耗尽。这可能导致超出范围的错误条件。
最后,分配器不能确保分配的存储正确对齐。底层缓冲区仅与 对齐alignof(int)
,如果容器分配其具有更高对齐要求的内部结构(例如,如果结构包含指针,并且指针大于 ),这可能还不够int
。
设计分配器时的一般建议是根据字节的原始存储来实现它们。该存储可用于创建不同类型、大小和对齐要求的对象,这些对象可以通过分配器的不同副本进行分配(在重新绑定到其他类型之后)。从这个意义上说,你传递给容器的分配器类型只是一个句柄,它可以被容器在它认为合适的时候反弹和复制。
推荐阅读
- ios - 停止重用单元格以在 Ios 中获取新数据
- php - SQL Select,每分钟只有一条记录
- python - 为什么我的 Python 程序在 IDE(pycharm) 中运行,但当我从命令行尝试时却没有?
- ruby-on-rails - 更新关联的记录属性不起作用
- html - 带有标题和副标题标签的单选按钮
- ruby-on-rails - 在两个模型之间创建关联对象时的未知属性
- android - 从特定站点 Xamarin Android 重定向到应用程序
- angular - 如何使用 angular2 在单击该选项卡时激活该选项卡
- apache-spark - Spark 结构化流写入 Kinesis Fiehose
- airflow - 气流执行相同的运算符两次