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

时间:2012-05-22 09:26:12

标签: c++ c++11 c++-faq

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

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

这是前一个: eC++ Container choice

4 个答案:

答案 0 :(得分:92)

不是我知道的,但是我猜它可以用文字方式完成。此外,图表略有偏差,因为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是唯一的,因此不会出现重复。

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


    问题2:内存稳定

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

    问题2.1:哪个

    • 结算list; forward_list只对较小的内存占用有用。

    问题3:动态调整大小

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

    问题4:双端

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

    默认情况下,除非您需要关联容器,否则您将选择vector。事实证明它也是Sutter and Stroustrup's recommendation

答案 1 :(得分:48)

我喜欢Matthieu的答案,但我要重申流程图:

何时不使用std :: vector

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

构造

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

std::deque是一般替代品,具有std::vector的许多属性,但您只能在双端队列的两端插入。中间的插入物需要移动。 std::list对其内容没有要求。

需要Bools

std::vector<bool>是......不是。嗯,这是标准的。但它通常意义上不是vector,因为std::vector通常允许的操作是被禁止的。而且它肯定不包含bool s

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

搜索

如果您需要在容器中查找元素,并且搜索代码不能只是一个索引,那么您可能需要放弃std::vector以支持setmap 。注意关键字“可能”;排序std::vector有时是合理的选择。或者Boost.Container的flat_set/map,它实现了一个排序的std::vector

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

  • 当搜索标记与您要查找的项目不同时,请使用map。否则请使用set
  • 如果容器中包含 lot 项目,则使用unordered,搜索效果绝对必须为O(1),而不是O(logn)
  • 如果您需要多个项目来拥有相同的搜索标记,请使用multi

订购

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

或者您可以使用已排序的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插入性能可能没有以前那么糟糕。一些实现实现了一种基于移动语义的项目复制(即所谓的“交换优化”),但现在移动是语言的一部分,它是标准规定的。

无动态分配

如果您想要尽可能少的动态分配,

std::array是一个很好的容器。它只是一个C阵列的包装器;这意味着它的大小必须在编译时中知道。如果您能接受,请使用std::array

话虽这么说,使用std::vectorreserve大小对于有界std::vector也同样有用。这样,实际大小可能会有所不同,您只能获得一次内存分配(除非您放弃容量)。

答案 2 :(得分:23)

以上是上述流程图的C ++ 11版本。 [最初发布时没有归属于原作者Mikael Persson]

答案 3 :(得分: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)大量元素需要自定义算法,而不是不同的容器。