我喜欢 Matthieu 的回答,但我将重述流程图如下:
何时不使用 std::vector
默认情况下,如果您需要一个容器,请使用std::vector。因此,每个其他容器只有通过提供一些替代 std::vector 的功能来证明是合理的。
构造函数
std::vector 要求其内容是可移动构造的,因为它需要能够随机播放项目。这对内容来说并不是一个可怕的负担(请注意,默认构造函数不需要,这要感谢emplace 等等)。但是,大多数其他容器不需要任何特定的构造函数(再次感谢emplace)。因此,如果您有一个绝对不能实现移动构造函数的对象,那么您将不得不选择其他东西。
std::deque 将是一般替换,具有std::vector 的许多属性,但您只能在双端队列的任一端插入。中间的插入物需要移动。 std::list 对其内容没有任何要求。
需要布尔
std::vector<bool> 是……不是。嗯,这是标准的。但它不是通常意义上的vector,因为std::vector 通常允许的操作是被禁止的。而且它肯定不包含bools。
因此,如果您需要来自bools 容器的真实vector 行为,您将无法从std::vector<bool> 获得它。所以你必须使用std::deque<bool>。
搜索
如果您需要在容器中查找元素,并且搜索标签不能只是索引,那么您可能需要放弃std::vector,转而使用set 和map。注意关键词“可能”;排序的std::vector 有时是一个合理的选择。或者Boost.Container的flat_set/map,它实现了一个排序的std::vector。
现在有四种变体,每种都有自己的需求。
- 当搜索标签与您要查找的项目本身不同时,请使用
map。否则使用set。
- 当容器中有很多个项目并且搜索性能绝对需要
O(1)而不是O(logn)时,请使用unordered。
- 如果您需要多个项目具有相同的搜索标签,请使用
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::vector 和reserve 确定大小对于有界std::vector 也同样有效。这样,实际大小可能会有所不同,并且您只能获得一个内存分配(除非您耗尽了容量)。