我有一个看起来像这样的课程:
typedef std::list<char*> PtrList;
class Foo
{
public:
void DoStuff();
private:
PtrList m_list;
PtrList::iterator m_it;
};
功能 DoStuff()
基本上添加元素 m_list
或删除它中的元素,找到一个特殊元素的迭代器并将其存储 m_it
。重要的是要注意每个值 m_it
用于以后的每个调用 DoStuff()
。
所以有什么问题?
一切正常,除了分析显示操作员 new
被调用太多了 list::push_back()
来自 DoStuff()
。
为了提高性能,我想为内存预分配内存 m_list
在初始化中 Foo
正如我会做的那样 std::vector
。问题是这会引入新的问题,例如:
- 效率较低
insert
和 erase
元素。
m_it
一旦将矢量从一次调用更改为,则变为无效 DoStuff()
到下一个。 编辑: Alan Stokes建议使用索引而不是迭代器来解决这个问题。
我的解决方案 我能想到的最简单的解决方案是实现一个具有链表功能的对象池。这样我就得到一个链表 和 可以为它预先分配内存。
我错过了什么或者它是否真的是最简单的解决方案?我宁愿不“重新发明轮子”,而是使用标准解决方案,如果存在的话。
任何想法,变通方法或启发性评论将不胜感激!
我认为你使用错误的容器。
如果你想快速推回然后不自动假设你需要一个链表,一个链表是一个缓慢的容器,它基本上适合重新排序。
一个更好的容器是std :: deque。 deque基本上是一个数组数组。它会分配一块内存并在你向后推时占用它,当它用完时会分配另一个块。这意味着它只是很少分配,你不必提前知道容器的大小,如std :: vector和 reserver
。
你可以使用 splice
功能 std::list
实现一个池。添加一个新的成员变量 PtrList m_Pool
。如果要添加新对象且池不为空,请将该值分配给池中的第一个元素,然后将其拼接到列表中。要擦除元素,请将其从列表中拼接到池中。
但如果你不关心元素的顺序,那么a deque
可以快得多。如果要删除中间的元素,请将最后一个元素复制到要删除的元素上,然后删除最后一个元素。
我的建议是一样的 111111
的,尝试切换到 deque
在你写任何重要的代码之前。
但是,要直接回答你的问题:你可以使用 std::list
使用自定义分配器。它有点繁琐,我不会在这里完成所有细节,但它的要点是你编写一个代表列表节点的内存分配策略的类。分配的节点 list
将是一个较小的实现定义量大于 char*
,但它们都将具有相同的大小,这意味着您可以为该大小编写一个优化的分配器(一个内存块池而不是一个对象池),并且您可以添加函数,让您保留任何空间想要在分配器中,在你想要的时候。然后列表可以快速分配/释放。这节省了您需要重新实现任何实际的 list
功能。
如果您(由于某种原因)要实现具有列表功能的对象池,那么您可以从这开始 boost::intrusive
。在编写自己的分配器时,这可能也很有用,可以跟踪您的空闲块列表。
可以使用 list::get_allocator().allocate()
。 Afaik,默认行为是由于非连续性而获取内存 list
s - 因此缺乏 reserve()
- 使用时没有重大缺点 allocator
方法立即发生在我身上。如果您的计划中有非关键部分,在开始时或其他任何部分,您至少可以选择在此时获取损害。
列表和向量在管理对象的方式上完全不同。
Vector将元素构造到给定容量的已分配缓冲区中。当容量耗尽时,会发生新的分配。
List逐个分配元素,每个元素分别放入一个空间。
插入/移除某些内容时,向量元素会移位,因此,向量索引和元素地址不稳定。
插入/删除某些内容时会重新链接List元素,因此列表迭代器和元素地址是稳定的。
使列表行为与向量类似的一种方法是替换默认分配器(每次调用时分配系统)与另一个分配较大块中的对象,在调用子块时将子块分派给列表它。
这不是标准库默认提供的内容。