【问题标题】:In which scenario do I use a particular STL container?在哪种情况下使用特定的 STL 容器?
【发布时间】:2010-10-03 01:18:31
【问题描述】:

我一直在阅读有关 C++ 的书中的 STL 容器,特别是有关 STL 及其容器的部分。现在我确实明白了它们中的每一个都有自己的特定属性,并且我已经快要记住所有这些属性了……但是我还没有掌握它们每个在什么场景中使用。

解释是什么?示例代码更受欢迎。

【问题讨论】:

标签: c++ stl container-data-type


【解决方案1】:

This cheat sheet 很好地总结了不同的容器。

请参阅底部的流程图作为在不同使用场景中使用的指南:

David Moorelicensed CC BY-SA 3.0创建

【讨论】:

  • 这个流程图是黄金,我希望我在 c# 中有类似的东西
  • 更新链接:C++ Containers Cheat Sheet.
  • 起点必须是vector 而不是空的。 stackoverflow.com/questions/10699265/…
  • 您现在有unordered_mapunordered_set(以及它们的多个变体),它们不在流程图中,但是当您不关心顺序但需要按键查找元素时,它们是不错的选择.他们的查找通常是 O(1) 而不是 O(log n)。
  • @shuttle87 不仅大小永远不会改变,更重要的是大小是在编译时确定的,永远不会改变。
【解决方案2】:

简单的答案:除非您有真正的理由不这样做,否则对任何事情都使用std::vector

当你发现一个你在想“哎呀,std::vector 因为 X 在这里不好用”的案例时,请在 X 的基础上继续。

【讨论】:

  • 但是..迭代时注意不要删除/插入项目...尽可能使用 const_iterator 以避免这种情况..
  • 嗯...我认为人们过度使用向量。原因是,“不起作用”的情况不会轻易发生——所以人们坚持使用最常用的容器并滥用它来存储列表、队列……在我看来——这与流程图相匹配——应该根据预期用途选择容器,而不是应用“一个似乎适合所有人”。
  • @Black Point 是,即使在理论上应该运行得更慢的操作上,向量通常也更快。
  • @Vardhan std::remove_if 几乎总是优于“迭代期间删除”方法。
  • 一些基准测试确实有助于让讨论不那么主观。
【解决方案3】:

我在另一个问题中回答了这个问题,该问题被标记为这个问题的重复。但是我觉得还是可以参考一些关于选择标准容器的好文章。

正如@David Thornley 回答的那样,如果没有其他特殊需求,std::vector 就是要走的路。这是 C++ 的创建者 Bjarne Stroustrup 在 2014 年的博客中给出的建议。

这是文章的链接 https://isocpp.org/blog/2014/06/stroustrup-lists

并引用那个,

而且,是的,我的建议是默认使用 std::vector。

在 cmets 中,用户 @NathanOliver 还提供了另一个不错的博客,其中有更具体的测量。 https://baptiste-wicht.com/posts/2012/12/cpp-benchmark-vector-list-deque.html

【讨论】:

    【解决方案4】:

    我重新设计了流程图,使其具有 3 个属性:

    1. 我认为 STL 容器分为 2 个主要类。基本容器以及那些利用基本容器来实施策略的容器。
    2. 首先流程图应该将决策过程划分为我们应该决定的主要情况,然后对每个案例进行详细说明。
    3. 一些扩展容器可以选择不同的基本容器作为它们的内容器。流程图应考虑可以使用每个基本容器的情况。

    流程图:

    this link 提供更多信息。

    【讨论】:

    • 嗯,我认为你的 std::array 应该是 std::unique_ptr<T[]>。快速总结:vector 具有可变大小,unique_ptr<T[]> 具有在创建时确定的大小,array 要求其大小是编译时常量。
    【解决方案5】:

    我学到的一个教训是:尝试将它包装在一个类中,因为在一个晴朗的日子改变容器类型会产生很大的惊喜。

    class CollectionOfFoo {
        Collection<Foo*> foos;
        .. delegate methods specifically 
    }
    

    它的前期成本并不高,并且当您想在有人对该结构执行 x 操作时中断时节省调试时间。

    为工作选择完美的数据结构:

    每个数据结构都提供了一些操作,这些操作的时间复杂度可能不同:

    O(1)、O(lg N)、O(N) 等

    您基本上必须做出最好的猜测,哪些操作将完成最多,并使用该操作为 O(1) 的数据结构。

    简单,不是吗(-:

    【讨论】:

    • 这不就是我们使用迭代器的原因吗?
    • @PlatinumAzure 甚至迭代器也应该是成员 typedef .. 如果您更改容器类型,您还必须更改所有迭代器定义......虽然这在 c++1x 中得到了修复!
    • 对于好奇的人,这是 C++11 中的修复:auto myIterator = whateverCollection.begin(); // &lt;-- immune to changes of container type
    • typedef Collection&lt;Foo*&gt; CollectionOfFoo; 就足够了吗?
    • 您不太可能稍后改变主意并简单地委托给不同的容器:Beware the illusion of container-independent code
    【解决方案6】:

    我扩展了Mikael Persson's 精彩的流程图。我添加了一些容器类别、数组容器和一些注释。如果您想要自己的副本,here 是 Google 绘图。谢谢,Mikael 做基础工作! C++ Container Picker

    【讨论】:

      【解决方案7】:

      到目前为止只简要提到的一个重点是,如果您需要连续内存(如 C 数组提供的),那么您只能使用 vectorarraystring

      如果大小在编译时已知,则使用array

      如果您只需要处理字符类型并且需要一个字符串,而不仅仅是一个通用容器,请使用string

      在所有其他情况下使用vector(无论如何,vector 应该是大多数情况下容器的默认选择)。

      通过所有这三个,您可以使用data() 成员函数来获取指向容器第一个元素的指针。

      【讨论】:

        【解决方案8】:

        这是一个受我创建的 David Moore 版本(见上文)启发的流程图,它(大部分)与新标准 (C++11) 保持一致。这只是我个人的看法,不是无可争辩的,但我认为它可能对这次讨论很有价值:

        【讨论】:

        • 可以提供原件吗?这是一张很棒的图表。也许坚持在博客或 GitHub 上?
        • 这是一张出色的图表。虽然有人可以向我解释“持续职位”是什么意思吗?
        • @S.T.A.L.K.E.R.持久位置意味着如果你有一个指向容器中元素的指针或迭代器,那么无论你在容器中添加或删除什么(只要它不是有问题的元素)。
        • 这确实是一张很棒的图表,但是我认为vector (sorted) 与其他图表有点不一致。它不是不同类型的容器,只是相同的std::vector,但已排序。更重要的是,如果这是通过集合进行迭代的标准行为,我不明白为什么不能使用std::set 进行有序迭代。当然,如果答案是关于有序地访问容器槽[] 的值,那么好吧,你只能使用soted std::vector 来做到这一点。但无论哪种情况,都应该在“需要订单”问题之后做出决定
        • @user2019840 我想将图表限制为标准容器。代替“排序向量”应该出现的是“flat_set”(来自Boost.Container)或等效项(每个主要库或代码库都有一个flat_set 等效项,AFAIK)。但这些都是非标准的,并且是 STL 中一个明显的遗漏。而您不想遍历 std::set 或 std::map 的原因(至少不经常)是它是very inefficient to do so
        【解决方案9】:

        看看 Scott Meyers 的 Effective STL。很好地解释了如何使用 STL。

        如果您想存储已确定/未确定数量的对象并且您永远不会删除任何对象,那么您需要的是矢量。它是 C 数组的默认替代品,它的工作原理与一个类似,但不会溢出。您也可以使用 reserve() 预先设置其大小。

        如果您想存储不确定数量的对象,但您将添加和删除它们,那么您可能需要一个列表...因为您可以在不移动任何后续元素的情况下删除一个元素 - 与矢量不同。但是,它比向量占用更多内存,而且您不能顺序访问元素。

        如果您想获取一堆元素并仅找到这些元素的唯一值,则将它们全部读入一个集合中即可,它也会为您排序。

        如果您有很多键值对,并且您想按键对它们进行排序,那么映射很有用……但每个键只能保存一个值。如果每个键需要多个值,则可以将向量/列表作为映射中的值,或使用多映射。

        它不在 STL 中,但它在 STL 的 TR1 更新中:如果您有很多键值对要通过键查找,并且您不关心它们的顺序,您可能想要使用散列 - 即 tr1::unordered_map。我在 Visual C++ 7.1 中使用过它,它被称为 stdext::hash_map。它的查找时间为 O(1),而不是查找地图的时间为 O(log n)。

        【讨论】:

        • 我现在听到一些轶事表明微软的hash_map 不是一个很好的实现。我希望他们在unordered_map 上做得更好。
        • 列表 - “你不能顺序访问一个元素。” - 我认为你的意思是你不能随机访问或直接索引到一个元素....
        • ^ 是的,因为顺序访问正是list 所做的。那里有明显的错误。
        【解决方案10】:

        这完全取决于您想要存储什么以及您想要对容器做什么。以下是我最常使用的容器类的一些(非常非详尽的)示例:

        vector:紧凑的布局,每个包含的对象几乎没有内存开销。有效地迭代。追加、插入和擦除可能会很昂贵,特别是对于复杂对象。通过索引查找包含的对象很便宜,例如我的向量[10]。在 C 中使用数组的地方使用。在有很多简单对象(例如 int)的地方很好。在向容器添加大量对象之前不要忘记使用reserve()

        list:每个包含的对象的内存开销很小。有效地迭代。追加、插入和擦除都很便宜。在 C 中使用链表的地方使用。

        set(和multiset):每个包含的对象的内存开销很大。在需要快速查明该容器是否包含给定对象或有效合并容器的地方使用。

        map(和multimap):每个包含的对象的内存开销很大。使用您想要存储键值对的位置并通过键快速查找值。

        zdan 建议的cheat sheet 上的流程图​​提供了更详尽的指南。

        【讨论】:

        • “每个包含对象的小内存开销”不适用于列表。 std::list 被实现为双向链表,因此它为每个存储的对象维护 2 个指针,这是不容忽视的。
        • 我仍然会将每个存储对象的两个指针计为“小”。
        • 与什么比较? std::forward_list 是一个容器,主要建议每个对象存储较少的元数据(只有一个指针)。而 std::vector 每个对象保存 0 个元数据。所以 2 指针与其他容器相比是不可协商的
        • 这完全取决于您的对象的大小。我已经说过 vector 有一个“紧凑的布局,每个包含的对象几乎没有内存开销”。我仍然会说与 set 和 map 相比,list 的内存开销较小,而与 vector 相比,内存开销略大。我不太确定你想达到什么目的!
        • 所有基于模式的容器由于动态分配而往往具有显着的开销,而动态分配很少是免费的。当然,除非您使用的是自定义分配器。
        猜你喜欢
        • 1970-01-01
        • 2014-03-24
        • 2021-11-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多