如何在C ++ 11中有效选择标准库容器?


135

有一个众所周知的图像(备忘单),称为“ C ++容器选择”。这是为所需用途选择最佳容器的流程图。

有人知道它是否已经有C ++ 11版本吗?

这是上一个: eC ++容器选择


6
以前从未见过。谢谢!
WeaselFox 2012年

6
@WeaselFox:关于SO ++,它已经是C ++-Faq的一部分。
Alok保存

4
C ++ 11仅引入了一种新的真实容器类型:unordered_X容器。包括它们只会大大混淆表,因为在确定哈希表是否合适时有许多注意事项。
Nicol Bolas'5

13
James是对的,有更多的情况要使用vector而不是表格所示。在许多情况下,数据局部性的优势胜过某些操作中效率低下的问题(不久之后为C ++ 11)。我不觉得E视力表,以便对C有用甚至++ 03
大卫·罗德里格斯- dribeas

33
这很可爱,但是我认为阅读任何有关数据结构的通用教科书都将使您处于一种状态,不仅可以在几分钟内重新创建该流程图,还可以了解该流程图所掩盖的更多有用信息。
安德鲁·托马佐斯

Answers:


97

我所不知道的,但是我想可以从文字上做。此外,图表略有偏离,因为list通常来说容器并不是一个很好的容器,也不是forward_list。这两个列表都是针对利基应用的非常专业的容器。

要构建这样的图表,您只需要两个简单的准则:

  • 首先选择语义
  • 当有几种选择时,请选择最简单的

首先,通常不必担心性能。仅当您开始处理数千个(或更多)项目时,才会真正考虑到O的大问题。

容器有两大类:

  • 关联容器:它们有一个find操作
  • 简单序列容器

然后你就可以在它们上面建几个适配器:stackqueuepriority_queue。我将把适配器放在这里,它们足够专业,可以识别。


问题1:联想

  • 如果您需要通过一个键轻松进行搜索,则需要一个关联容器
  • 如果需要对元素进行排序,则需要一个有序的关联容器
  • 否则,跳至问题2。

问题1.1:订购了吗?

  • 如果不需要特定的命令,请使用unordered_容器,否则请使用其传统的有序命令。

问题1.2:分隔键

  • 如果键与值分开,请使用map,否则请使用set

问题1.3:重复吗?

  • 如果要保留重复项,请使用multi,否则不要使用。

例:

假设我有几个具有唯一ID的人,并且我想尽可能简单地从其ID中检索人数据。

  1. 我想要一个find功能,因此需要一个关联容器

    1.1。我不在乎订单,因此是一个unordered_容器

    1.2。我的密钥(ID)与其关联的值是分开的,因此map

    1.3。该ID是唯一的,因此不能重复输入。

最终答案是:std::unordered_map<ID, PersonData>


问题2:内存稳定吗?

  • 如果元素在内存中应该是稳定的(即,在修改容器本身时它们不应四处移动),则使用一些 list
  • 否则,跳至问题3。

问题2.1:哪个

  • 定居list; a forward_list仅对减少内存占用量有用。

问题3:动态尺寸

  • 如果容器的大小已知(在编译时),并且在程序执行过程中不会更改此大小,并且元素是默认可构造的,或者您可以提供完整的初始化列表(使用{ ... }语法),则可以使用array。它取代了传统的C阵列,但具有便捷的功能。
  • 否则,跳至问题4。

问题4:双端

  • 如果希望能够从正面和背面移除物品,请使用deque,否则使用vector

您会注意到,默认情况下,除非需要关联容器,否则您的选择将是vector。事实证明,这也是Sutter和Stroustrup的推荐


5
+1,但有一些注意事项:1)array不需要默认的可构造类型;2)选择multis并不是说允许重复,而是更多关于是否保留它们很重要(您可以将重复项放入非multi容器中,恰好只有一个保留)。
R. Martinho Fernandes

2
这个例子有点偏离。1)如果我们需要“高效地”找到,我们可以在一个非关联容器上“查找”(不是成员函数,不是“ <算法”之一),1.1)如果要有效地找到,unordered_将是O(1)而不是O(登录n)。
BlakBat 2012年

4
@BlakBat:map.find(key)比起可口性要好得多std::find(map.begin(), map.end(), [&](decltype(map.front()) p) { return p.first < key; }));,因此从语义上讲,这find是一个成员函数,而不是来自的成员函数很重要<algorithm>。至于O(1)vs O(log n),它不影响语义;我将从示例中删除“有效”,然后将其替换为“轻松”。
Matthieu M.

“如果元素在内存中应该是稳定的……则使用一些列表” ……hmmm,我以为deque也具有此属性?
Martin Ba

@MartinBa:是和否。在a中deque,元素只有在任一端按下/弹出时才稳定;如果您开始在中间插入/删除,则最多将洗净N / 2个元素以填充所创建的间隙。
Matthieu M.

51

我喜欢Matthieu的回答,但是我将重新声明流程图,如下所示:

何时不使用std :: vector

默认情况下,如果您需要一个容器,请使用std::vector。因此,仅通过提供替代的某些功能来证明每个其他容器都是合理的std::vector

建设者

std::vector要求它的内容是可移动构造的,因为它需要能够随机播放周围的项目。这对内容来说并不是一个沉重的负担(请注意,由于等等,不需要默认的构造函数emplace)。但是,大多数其他容器不需要任何特定的构造函数(再次感谢emplace)。因此,如果您有一个绝对不能实现 move构造函数的对象,那么您将不得不选择其他东西。

A std::deque是具有的许多属性的常规替换,std::vector但是您只能在双端队列的两端插入。中间的插入物需要移动。一个std::list放在它的内容没有要求。

需要傻瓜

std::vector<bool>不是。好吧,这是标准的。但这不是vector通常意义上的,因为std::vector通常会允许进行禁止的操作。并且最肯定不包含bools

因此,如果您需要vector包含的容器的真实行为,则bool不会从中获取它std::vector<bool>。因此,您必须使用std::deque<bool>

正在搜寻

如果您需要在容器中查找元素,并且搜索标签不能只是索引,那么您可能需要放弃std::vector使用setmap。注意关键字“ 可能 ”;一个排序std::vector有时是合理的替代方案。或Boost.Container's flat_set/map,它实现了sorted std::vector

现在有四种变体,每种都有自己的需求。

  • map当搜索标签与您要查找的项目不同时,请使用a 。否则请使用set
  • 使用unordered时,你有很多在集装箱中的物品和搜索性能绝对必须O(1)的,而不是O(logn)
  • 使用multi,如果您需要多个项目具有相同的搜索标签。

定购

如果需要始终根据特定比较操作对项目容器进行排序,则可以使用set。或者,multi_set如果您需要多个项目具有相同的值。

或者,您可以使用sorted std::vector,但是必须保持其排序。

稳定性

当迭代器和引用无效时,有时会引起关注。如果您需要一个项目列表,以便在其他各个地方都具有这些项目的迭代器/指针,则std::vector无效的方法可能不合适。任何插入操作都可能导致无效,具体取决于当前的大小和容量。

std::list提供了有力的保证:迭代器及其关联的引用/指针仅在将项目本身从容器中移除时才无效。std::forward_list如果内存是一个严重的问题,那儿有吗。

如果担保太强,则std::deque提供较弱但有用的担保。无效是由中间的插入导致的,但是在头部或尾部的插入只会导致迭代器无效,而不会导致对容器中项目的指针/引用无效。

插入性能

std::vector 仅在最后提供廉价的插入(即使那样,如果您增加容量也会变得昂贵)。

std::list就性能而言是昂贵的(每个新插入的项都需要分配内存),但是它是一致的。它还偶尔提供必不可少的功能,可以在不影响性能的情况下随机整理物品,以及与其他std::list相同类型的容器交易物品而不会降低性能。如果您需要对周围事物进行大量改组,请使用std::list

std::deque在头部和尾部提供固定时间的插入/拔出,但在中间插入可能会相当昂贵。因此,如果您需要从正面和背面添加/删除东西,std::deque可能就是您所需要的。

应该注意的是,由于移动语义的原因,std::vector插入性能可能不会像以前那样糟糕。一些实现实现了一种基于移动语义的项目复制形式(所谓的“ swaptimization”),但是现在移动是语言的一部分,它是标准的强制要求。

没有动态分配

std::array如果您想要最少的动态分配,则是一个很好的容器。它只是C数组的包装;这意味着必须在编译时知道其大小。如果可以忍受,请使用std::array

话虽这么说,使用std::vector和调整reserve大小对于有界也同样适用std::vector。这样,实际大小可能会有所不同,并且只有一个内存分配(除非您消耗了容量)。


1
好吧,我也非常喜欢您的回答:) WRT保持对向量进行排序,除了之外std::sort,还有std::inplace_merge一个很有趣的地方就是可以轻松放置新元素(而不是std::lower_bound+ std::vector::insert调用)。很高兴了解flat_setflat_map
Matthieu M.

2
您也不能使用具有16字节对齐类型的向量。同时也是一个很好的替代品vector<bool>vector<char>
2012年

@Inverse:“您也不能使用具有16字节对齐类型的向量。” 谁说的?如果std::allocator<T>不支持这种对齐方式(并且我不知道为什么不支持这种对齐方式),那么您始终可以使用自己的自定义分配器。
尼科尔·波拉斯

2
@Inverse:C ++ 11 std::vector::resize具有一个不带值的重载(它只是采用新的大小;任何新元素都将是就地默认构造的)。另外,为什么编译器即使声明为具有对齐方式也无法正确对齐值参数?
尼科尔·波拉斯

1
bitsetfor bool,如果您事先知道大小,请访问en.cppreference.com/w/cpp/utility/bitset
bentervader


1

快速旋转,尽管可能需要工作

Should the container let you manage the order of the elements?
Yes:
  Will the container contain always exactly the same number of elements? 
  Yes:
    Does the container need a fast move operator?
    Yes: std::vector
    No: std::array
  No:
    Do you absolutely need stable iterators? (be certain!)
    Yes: boost::stable_vector (as a last case fallback, std::list)
    No: 
      Do inserts happen only at the ends?
      Yes: std::deque
      No: std::vector
No: 
  Are keys associated with Values?
  Yes:
    Do the keys need to be sorted?
    Yes: 
      Are there more than one value per key?
      Yes: boost::flat_map (as a last case fallback, std::map)
      No: boost::flat_multimap (as a last case fallback, std::map)
    No:
      Are there more than one value per key?
      Yes: std::unordered_multimap
      No: std::unordered_map
  No:
    Are elements read then removed in a certain order?
    Yes:
      Order is:
      Ordered by element: std::priority_queue
      First in First out: std::queue
      First in Last out: std::stack
      Other: Custom based on std::vector????? 
    No:
      Should the elements be sorted by value?
      Yes: boost::flat_set
      No: std::vector

你可能会注意到,这不同于似地从C ++ 03版本,主要是由于这样的事实,我真的不喜欢链接节点。链接节点容器在性能上通常会被非链接容器击败,除了极少数情况下。如果您不知道这些情况是什么,并且有权使用Boost,请不要使用链接节点容器。(std :: list,std :: slist,std :: map,std :: multimap,std :: set,std :: multiset)。该列表主要侧重于中小型容器,因为(A)这是我们在代码中处理的99.99%,并且(B)大量元素需要自定义算法,而不是不同的容器。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.