【问题标题】:To STL or !STL, that is the question [closed]对于 STL 或!STL,这就是问题 [关闭]
【发布时间】:2010-09-15 12:34:58
【问题描述】:

毫无疑问,我会选择将 STL 用于大多数 C++ 编程项目。然而,最近有人向我提出了这个问题,“有没有什么情况下你不会使用 STL?”...

我想得越多,我就越意识到可能存在我选择不使用 STL 的情况......例如,一个非常大的长期项目,其代码库预计将持续数年...... . 也许一个完全适合项目需求的定制容器解决方案值得最初的开销?你怎么看,有没有什么情况下你会选择不使用 STL?

【问题讨论】:

    标签: c++ stl containers


    【解决方案1】:

    不使用 STL 的主要原因是:

    1. 您的 C++ 实现已经过时,并且模板支持很糟糕。
    2. 您不能使用动态内存分配。

    这两者在实践中都是非常不常见的要求。

    对于一个长期项目,滚动您自己的与 STL 功能重叠的容器只会增加维护和开发成本。

    【讨论】:

    • 归根结底就是这样。
    • 虽然我同意,但我也会把并发作为不使用容器的理由。但是,我会将它们用于任何非共享资源。
    • Greg - (1) 可能非常罕见,但 (2) 是高度情境化的。在某些开发环境(例如嵌入式平台和实时系统)中,动态分配要么被禁止,要么被强烈反对。它们可能不是大环境,但在环境中是通用的。
    • 不想要异常可能是不使用 STL 的另一个原因
    • @sbk:您可以毫无例外地使用 STL。 GCC 至少将throw 实现为一个宏,该宏定义为在设置-fno-exceptions 时实际上不会抛出。
    【解决方案2】:

    具有严格内存要求的项目(例如嵌入式系统)可能不适合 STL,因为它可能难以控制和管理从堆中取出和返回的内容。正如 Evan 所提到的,编写适当的分配器可以帮助解决这个问题,但是如果您要计算使用的每个字节或关注内存碎片,那么手动推出针对您的特定问题量身定制的解决方案可能更明智,因为 STL 已经过优化用于最一般的用法。

    对于特定情况,您也可以选择不使用 STL,因为存在更多适用的容器,这些容器不在当前标准中,例如 boost::array 或 boost::unordered_map。

    【讨论】:

    • 我总是发现 SDL 容器都非常轻量级。查看您的 STL 实现,了解它们实际使用了多少内存。
    • 相当。如果 STL 可以满足您的需求,请使用它。当它不再满足您的需求时,您应该考虑不使用它。
    • boost::arrayboost::unordered_map 现在都以 std::arraystd::unordered_map 的名称存在于 STL 中。
    【解决方案3】:

    使用 stl 有很多好处。对于长期项目而言,收益大于成本。

    1. 新程序员从一开始就能够理解容器,这让他们有更多时间学习项目中的其他代码。 (假设他们已经像任何称职的 C++ 程序员一样了解 STL)
    2. 修复容器中的错误会浪费时间,而这些时间可以用来增强业务逻辑。
    3. 很可能您不会编写它们,因为 STL 已经实现了。

    话虽如此,STL 容器根本不处理并发。因此,在需要并发的环境中,我会使用其他容器,例如 Intel TBB 并发容器。这些使用细粒度锁定要先进得多,这样不同的线程可以同时修改容器,而您不必序列化对容器的访问。

    【讨论】:

    • 修复容器错误是乐趣!我非常不同意断言#2。 (不过,根据一点经验,我非常同意 #1。)
    【解决方案4】:

    通常,我发现最好的办法是使用带有自定义分配器的 STL,而不是用手动滚动的容器替换 STL 容器。 STL 的好处是您只需为使用的内容付费。

    【讨论】:

      【解决方案5】:

      我认为这是典型的构建 vs 购买场景。但是,我认为在这种情况下,我几乎总是会“购买”并使用 STL - 或更好的解决方案(也许来自 Boost),然后再推出自己的解决方案。您应该将大部分精力集中在应用程序的功能上,而不是它使用的构建块上。

      【讨论】:

        【解决方案6】:

        我真的不这么认为。在制作我自己的容器时,我什至会尝试使它们与 STL 兼容,因为通用算法的力量太大而不能放弃。 STL 至少应该在名义上使用,即使您所做的只是编写自己的容器并专门针对它的每个算法。这样,每个排序算法都可以调用 sort(c.begin(), c.end())。如果您专门排序以产生相同的效果,即使它的工作方式不同。

        【讨论】:

          【解决方案7】:

          为 Symbian 编码。

          STLPort 确实支持 Symbian 9,所以反对使用 STL 的理由比以前弱(“它不可用”是一个非常有说服力的理由),但 STL 对所有 Symbian 库来说仍然是陌生的,所以可能更多麻烦的不仅仅是用 Symbian 的方式做事。

          当然,基于这些理由可能会争辩说,为 Symbian 编码不是“C++ 编程项目”。

          【讨论】:

          • Symbian API 让我相信他们既不懂 C++ 也不懂移动软件开发。
          • 我不会特别为它辩护,只是说它已经有 10 年或更长时间了,而且似乎是为比实际使用的设备更受限制的设备设计的。甚至诺基亚也在他们的低端设备上使用了 S40,这引发了一个问题,为什么 SymbianOS 会做出这样的假设。
          • 例如,在 32MB RAM 的智能手机上,作为程序员,我不热衷于做额外的工作来避免每个字符串 4 个字节的内存开销。不过,我想这在当时可能是有道理的。他们主要将 C++ 视为带有类的 C,这在当今并不流行。
          【解决方案8】:

          我参与过的大多数项目的代码库都比任何真正可用的 STL 版本都要老 - 因此我们选择现在不引入它。

          【讨论】:

          • 为什么?您确定要使用不能与 STL 一起使用的旧编译器吗?
          • @beldaz:不。我们有自己开发的集合和字符串类,将它们与 STL 混合没有什么意义。重写一切以使用 STL 在理论上会很好,但没有商业案例来支持这样的项目。
          • 有趣的案例,我可以看到你来自哪里。在那里你无能为力。
          【解决方案9】:

          简介:

          STL 是一个很棒的库,在许多情况下都很有用,但它肯定不能解决所有情况。回答 STL 或 !STL 就像回答“STL 是否满足您的需求?”

          STL 的优点

          • 在大多数情况下,STL 都有一个适合给定解决方案的容器。
          • 有据可查
          • 众所周知(程序员通常已经知道,进入项目更短)
          • 经过测试且稳定。
          • 是跨平台的
          • 每个编译器都包含它(不添加第三个库依赖项)
          • STL 已实施并准备就绪
          • STL 是闪亮的,...

          STL 的对比

          您需要一个简单的图表、红黑树或一个非常复杂的元素数据库以及通过量子计算机管理并发访问的 AI,这并不重要。事实是,STL 不会,也永远不会解决所有问题。

          以下方面只是几个例子,但它们基本上是这个事实的结果:STL 是一个真正的库。

          • 异常:STL 中继异常,因此如果出于任何原因您不能接受异常(例如安全关键),则不能使用 STL。正确的!异常可能被禁用,但这并不能解决 STL 在它们上中继的设计,最终会导致崩溃。

          • 需要特定(尚未包含)的数据结构:图、树等

          • 复杂性的特殊限制:您会发现 STL 通用容器并不是最适合您的瓶颈代码的。

          • 并发注意事项:要么您需要并发,而 STL 不提供您需要的东西(例如,由于双向 [] operator,不能(轻松)使用读写器锁)。您可以设计一个容器,利用多线程来更快地访问/搜索/插入/任何东西。

          • STL 需要满足您的需求,但反过来也是如此:您需要满足 STL 的需求。不要尝试在具有 1K 非托管 RAM 的嵌入式微控制器中使用 std::vector

          • 与其他库的兼容性:可能是由于历史原因,您使用的库不接受 STL(例如 QtWidgets 大量使用它自己的 QList)。双向转换容器可能不是最佳解决方案。


          实现您自己的容器

          读完之后,你可能会想:“好吧,我确信我可以为我的具体情况做一些比 STL 做得更好的事情。”等等!

          正确实施您的容器很快就会成为一项艰巨的任务:这不仅仅是实施一些可行的事情,您可能还必须:

          • 深入记录,包括限制、算法复杂度等。
          • 期待错误并解决它们
          • 传入的额外需求:您知道,缺少此功能,类型之间的转换等。
          • 一段时间后,您可能想要重构并更改所有依赖项(为时已晚?)
          • ....

          像容器一样在代码深处使用的代码肯定需要时间来实现,并且应该谨慎。


          使用第 3 方库

          不是 STL 并不一定意味着定制。网络上有很多不错的库,有些甚至具有宽松的开源许可证。

          添加或不添加额外的第三方库是另一个主题,但值得考虑。

          【讨论】:

            【解决方案10】:

            可能发生这种情况的一种情况是,当您已经在使用已经提供了 STL 所需功能的外部库时。例如,我的公司在空间有限的区域开发了一个应用程序,并且已经将 Qt 用于窗口工具包。由于 Qt 提供了类似 STL 的容器类,因此我们使用它们而不是将 STL 添加到我们的项目中。

            【讨论】:

            • 鉴于 STL 作为 C++ 的标准部分包含在内,但是,与其说您不将 STL 添加到您的项目中,不如说您选择不利用已经存在的东西那里。这很好。
            【解决方案11】:

            我发现在多线程代码中使用 STL 存在问题。即使您不跨线程共享 STL 对象,许多实现也使用非线程安全的构造(例如 ++ 用于引用计数而不是互锁递增样式,或者具有非线程安全的分配器)。

            在每种情况下,我仍然选择使用 STL 并解决问题(有足够的钩子来获得你想要的东西)。

            即使您选择制作自己的集合,最好遵循 STL 样式的迭代器,这样您就可以使用仅在迭代器上运行的算法和其他 STL 函数。

            【讨论】:

            • 我不明白。如果您的 STL 对象不是跨线程共享的,那么是否使用互锁增量又有什么关系呢?
            • 您可能在使用 VC++ 6.0 吗?在线程安全方面,该版本的 STL 实现有缺陷。
            • 是的——但这不是唯一的。在 Solaris 和 HP 上——我遇到了类似的问题。
            • 否 - 再次阅读。一个线程中的一个对象,另一个线程中的一个副本。两个对象,两个线程。还是个bug。 STL 是标准 C++ 的一部分,它没有线程。线程安全 STL 是常见的供应商扩展;使用 C++0x,我们会期待更多。
            • 问题是共享完全在字符串抽象之后发生——我可能会导致严格的 const 字符串和只读操作出现问题——因为在内部,字符串共享一个引用计数,只需复制构造(传递和返回字符串)会导致问题。
            【解决方案12】:

            我看到的主要问题是必须与依赖非抛出运算符 new 的遗留代码集成。

            【讨论】:

              【解决方案13】:

              我大约在 1984 年左右开始编写 C 语言并且从未使用过 STL。多年来,我推出了自己的函数库,当 STL 还不稳定或缺乏跨平台支持时,它们已经发展壮大。我的通用库已经发展到包含其他人的代码(主要是 libjpeg、libpng、ffmpeg、mysql 等)和其他一些代码,我宁愿将其中的外部代码量保持在最低限度。我确信现在 STL 很棒,但坦率地说,我对我的工具箱中的项目很满意,并且认为此时不需要加载更多工具。但我确实看到了新程序员可以通过使用 STL 实现的巨大飞跃,而无需从头开始编写所有代码。

              【讨论】:

              • 只有一个问题:你是用 C 编码还是用 C++ 编码?
              • STL 不是外部代码。它是所有符合标准的 C++ 编译器提供的标准部分。鉴于您的工具箱显然已经过时了十多年,可能值得您花时间环顾四周,看看还有哪些其他进展。好吧,我在这里开玩笑,我知道 EA 对 STL 有过或有过合理的厌恶。但是您将 STL 描述为“外部”是错误的。
              【解决方案14】:

              标准 C++ 反常地允许一些 iterator operations to throw exceptions 的实现。在某些情况下,这种可能性可能会出现问题。因此,您可以实现自己的简单容器,保证不会为关键操作抛出异常。

              【讨论】:

                【解决方案15】:

                由于在我之前回答的几乎每个人似乎都对 STL 容器如此热衷,我认为根据我自己遇到的实际问题编制一份不使用它们的充分理由列表会很有用。

                这些可以合理地分为三大类:

                1) 效率低

                STL 容器通常运行速度较慢并且为作业占用过多内存。造成这种情况的部分原因可以归咎于底层数据结构和算法的过于通用的实现,额外的性能成本源自与手头任务无关的大量 API 要求所需的所有额外设计约束。

                不计后果的内存使用和低劣的性能齐头并进,因为 CPU 以 64 字节的行在缓存上寻址内存,如果您不利用局部性引用来发挥自己的优势,那么您会浪费周期和宝贵的 Kb缓存内存。

                例如,std::list 每个元素需要 24 个字节,而不是最佳的 4 个。

                https://lemire.me/blog/2016/09/15/the-memory-usage-of-stl-containers-can-be-surprising/

                这是因为它是通过打包两个 64 位指针、1 个 int 和 4 个字节的内存填充来实现的,而不是像分配少量连续内存并单独跟踪正在使用的元素或使用指针异或技术将两个迭代方向存储在一个指针中。

                https://en.wikipedia.org/wiki/XOR_linked_list

                根据您的程序需求,这些低效率可能而且确实会造成很大的性能损失。

                2) 限制/爬行标准

                当然,有时问题是您需要一些完全通用的函数或稍微不同的容器类,而这些容器类只是没有在 STL 中实现,例如优先级队列中的 reduce_min()。

                一种常见的做法是,然后将容器包装在一个类中,并通过容器外部的额外状态和/或对容器方法的多次调用自己实现缺少的功能,这可能会模拟所需的行为,但性能要好得多比数据结构的实际实现更低且 O() 复杂度更高,因为无法扩展容器的内部工作。或者,您最终将两个或多个不同的容器混在一起,因为您同时需要两个或多个在任何给定 STL 容器中根本不兼容的东西,例如 minmax 堆、trie(因为您需要能够使用不可知论的指针) 等。

                这些解决方案可能很丑陋,而且效率低下,但语言发展的趋势是只添加新的 STL 方法来匹配 C++ 的特性蔓延,并忽略任何缺失的核心功能。

                3) 并发/并行

                STL 容器不是线程安全的,更不用说并发了。在当今 16 线程消费 CPU 的时代,令人惊讶的是,现代语言的默认容器实现仍然需要您围绕每个内存访问编写互斥锁,就像 1996 年一样。对于任何非平凡的并行程序来说,这是一种重要的是,因为内存屏障会迫使线程序列化它们的执行,如果这些发生的频率与 STL 调用相同,那么您可以告别并行性能。

                简而言之,只要您不关心性能、内存使用、功能或并行性,STL 就很好。 STL 当然仍然很好,因为很多时候您不受任何这些问题的约束,而可读性、可移植性、可维护性或编码速度等其他优先事项优先。

                【讨论】:

                  猜你喜欢
                  • 2011-07-27
                  • 1970-01-01
                  • 2013-08-19
                  • 1970-01-01
                  • 2012-12-13
                  • 2010-09-29
                  • 1970-01-01
                  • 2011-01-24
                  • 2012-05-13
                  相关资源
                  最近更新 更多