问题 对于std :: list,是否有一个等价的vector :: reserve()?


我有一个看起来像这样的课程:

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。问题是这会引入新的问题,例如:

  1. 效率较低 insert 和 erase 元素。
  2. m_it 一旦将矢量从一次调用更改为,则变为无效 DoStuff() 到下一个。 编辑: Alan Stokes建议使用索引而不是迭代器来解决这个问题。

我的解决方案 我能想到的最简单的解决方案是实现一个具有链表功能的对象池。这样我就得到一个链表  可以为它预先分配内存。

我错过了什么或者它是否真的是最简单的解决方案?我宁愿不“重新发明轮子”,而是使用标准解决方案,如果存在的话。

任何想法,变通方法或启发性评论将不胜感激!


6847
2018-05-20 14:32


起源

你究竟在用什么char *? - ScarletAmaranth
@ScarletAmaranth这些是指向内存中存储数据包的位置的指针(我将其简化为问题的char *)。 - Eitan T
如果你只使用一个,你有没有检查它是否真的表现更好 vector?它可能会做;快速分配和良好的缓存行为通常可以弥补插入/删除成本。 - Alan Stokes
您可以指定自定义 Allocator (第二个参数) list 模板)并使用它从您的池分配。你可能会发现 boost::pool 有用。 - Alan Stokes
@Eitan你可以存储索引而不是迭代器 vector 避免失效? (插入和删除时要小心。) - Alan Stokes


答案:


我认为你使用错误的容器。

如果你想快速推回然后不自动假设你需要一个链表,一个链表是一个缓慢的容器,它基本上适合重新排序。

一个更好的容器是std :: deque。 deque基本上是一个数组数组。它会分配一块内存并在你向后推时占用它,当它用完时会分配另一个块。这意味着它只是很少分配,你不必提前知道容器的大小,如std :: vector和 reserver


6
2018-05-20 14:57



我需要快速 erase 除了快 push_back。 - Eitan T
你真的对此进行了基准测试吗? erase 的 std::deque 反对的 std::list。 std::list 与连续的存储容器相比,它实际上非常慢。你的套装有多大。 - 111111
没有基准测试,没有。我的集合将包含大约10,000个元素。 - Eitan T
如果你实现的话,我会建议对它进行基准测试 std::deque 能够从单个块中擦除(或者它只需要在该块中移动元素)然后如果速度较慢我会感到惊讶。 - 111111
我怀疑 insert 和 erase 会更快 std::list 而不是 std::deque,但也可以通过更有效的分配补偿 deque。我也试一试。 - Eitan T


你可以使用 splice 功能 std::list 实现一个池。添加一个新的成员变量 PtrList m_Pool。如果要添加新对象且池不为空,请将该值分配给池中的第一个元素,然后将其拼接到列表中。要擦除元素,请将其从列表中拼接到池中。

但如果你不关心元素的顺序,那么a deque 可以快得多。如果要删除中间的元素,请将最后一个元素复制到要删除的元素上,然后删除最后一个元素。


4
2018-05-21 18:52





我的建议是一样的 111111的,尝试切换到 deque 在你写任何重要的代码之前。

但是,要直接回答你的问题:你可以使用 std::list 使用自定义分配器。它有点繁琐,我不会在这里完成所有细节,但它的要点是你编写一个代表列表节点的内存分配策略的类。分配的节点 list将是一个较小的实现定义量大于 char*,但它们都将具有相同的大小,这意味着您可以为该大小编写一个优化的分配器(一个内存块池而不是一个对象池),并且您可以添加函数,让您保留任何空间想要在分配器中,在你想要的时候。然后列表可以快速分配/释放。这节省了您需要重新实现任何实际的 list 功能。

如果您(由于某种原因)要实现具有列表功能的对象池,那么您可以从这开始 boost::intrusive。在编写自己的分配器时,这可能也很有用,可以跟踪您的空闲块列表。


2
2018-05-20 16:21



+1:谢谢你的建议。我想节省时间,所以在我决定写任何复杂的东西之前,我想听听其他建议。我按照111111的建议切换到了 deque。到目前为止,它对我有用(主要是因为我的大多数插入都是 push_backs而不是中间)。 - Eitan T


可以使用 list::get_allocator().allocate()。 Afaik,默认行为是由于非连续性而获取内存 lists - 因此缺乏 reserve()  - 使用时没有重大缺点 allocator 方法立即发生在我身上。如果您的计划中有非关键部分,在开始时或其他任何部分,您至少可以选择在此时获取损害。


0
2018-05-20 14:42





列表和向量在管理对象的方式上完全不同。

Vector将元素构造到给定容量的已分配缓冲区中。当容量耗尽时,会发生新的分配。 List逐个分配元素,每个元素分别放入一个空间。

插入/移除某些内容时,向量元素会移位,因此,向量索引和元素地址不稳定。 插入/删除某些内容时会重新链接List元素,因此列表迭代器和元素地址是稳定的。

使列表行为与向量类似的一种方法是替换默认分配器(每次调用时分配系统)与另一个分配较大块中的对象,在调用子块时将子块分派给列表它。 这不是标准库默认提供的内容。


0
2018-05-20 15:09



你想说的 列表迭代器 很稳定吧? - Eitan T
@EitanT:“这不是标准库默认提供的内容”。你不明白吗? - Nicol Bolas
@NicolBolas我不是在讨论标准库本身,也许还有其他“标准”实现用于此类目的。当然,我不是第一个想要预先分配内存的人 std::list... - Eitan T
@NicolBolas几年前我写了这个:[codeproject.com/Articles/13265/...:可能它需要一些调整来适应C ++ 11风格,但它应该工作! - Emilio Garavaglia
@EitanT:“你的意思是......”......哎呀,刚刚修好了!抱歉 - Emilio Garavaglia