15

在阅读了这个问题并在这里查看了一些结果之后,似乎应该完全避免使用 C++ 中的列表。我一直期望链表将成为我只需要遍历所有内容的情况下的首选容器,因为插入是指针操作的问题,并且永远不需要重新分配。

显然,由于“缓存局部性”,列表的迭代速度非常慢,因此必须使用更少的保留内存或更快的添加(从第二个链接看来,这似乎并没有那么快)带来的任何好处似乎都不值得它。

话虽如此,从性能的角度来看,我什么时候应该使用std::listoverstd::deque或者,如果可能的话,std::vector

附带说明一下,std::forward_list还会有很多缓存未命中吗?

4

4 回答 4

21

std::list在一些极端情况下很有用。

但是,C++ 顺序容器的一般规则是“如果您的算法兼容,请使用std::vector. 如果您的算法不兼容,请修改您的算法以便您可以使用std::vector.”

存在例外情况,这里尝试详尽列出std::list更好选择的原因:

  1. 当您需要 (A) 插入容器中间和 (B) 您需要内存中的每个对象位置保持稳定时。要求 (B) 通常可以通过使用由指向元素的指针组成的不稳定容器来删除,因此这不是使用std::list.

  2. 当您需要 (A) 在容器中间插入或删除 (B) 时,数量级比您需要迭代容器的次数多。这也是一个极端的极端情况:为了从 a 中找到要删除的元素list,您通常需要迭代!

这导致

  1. 您需要 (A) 在容器中间插入或删除,并且 (B) 让所有其他元素的迭代器保持有效。这最终成为案例 1 和案例 2 的隐藏要求:当您没有持久迭代器时,删除或插入的频率比迭代的频率高,并且迭代器和对象的稳定性高度相关。

  2. 最后一个案例,是拼接的案例曾经是使用的一个理由std::list

回到 C++03,所有版本的std::list::splice(理论上)都可以在 O(1) 时间内完成。然而,极其有效的形式splice要求size是 O(n) 操作。C++11 要求sizelistO(1) 上,因此splice的极端效率仅限于“拼接整个其他列表”和“自拼接子列表”的情况。在单个元素拼接的情况下,这只是插入和删除。在子范围拼接的情况下,代码现在必须访问拼接中的每个节点来计算它们以保持sizeO(1)(自拼接除外)。

因此,如果您只进行整体list拼接或自列表子范围splicelist则可以比其他非列表容器更快地完成这些操作。

于 2013-08-26T17:18:48.663 回答
19

从性能的角度来看,我什么时候应该使用std::list

从性能的角度来看,很少。唯一想到的情况是,如果您需要拆分和连接许多列表以形成其他列表;链表可以在不分配内存或移动对象的情况下做到这一点。

真正的好处list是稳定性:元素不需要是可移动的,并且迭代器和引用永远不会失效,除非它们引用了一个被擦除的元素。

附带说明一下,std::forward_list还会有很多缓存未命中吗?

是的; 它仍然是一个链表。

于 2013-08-26T16:56:58.953 回答
9

可以提供的最大改进std::list是当您将一个或多个元素从一个列表的中间移动到另一个列表时。此splice操作非常有效,list虽然它可能涉及随机访问容器中的项目的分配和移动,例如vector. 也就是说,就性能和简单性而言,这种情况很少发生,而且大部分时间vector都是您最好的容器。

如果您怀疑容器选择存在性能问题,请始终分析您的代码。

于 2013-08-26T16:57:54.670 回答
6

当您经常在容器中间添加/删除项目时,您选择std::liststd::dequestd::vector(以及其他连续的内存容器),另请参阅.

于 2013-08-26T16:55:22.863 回答