【问题标题】:some containers in stl don't have find functionstl 中的某些容器没有查找功能
【发布时间】:2014-10-26 09:03:39
【问题描述】:

一些 STL 容器,例如 std::liststd::vector 没有 find() 方法作为成员函数。这是为什么?我知道有从<algorithm> 使用std::find 的替代方法,但这种用法仍然不是100% 自然的。

【问题讨论】:

  • 可能是因为在顺序容器中搜索元素都有一些算法,所以它变得很常见,玩迭代器
  • @POW 不会阻止标准实施find(),对吗?我的意思是这可能只是打电话给std::find()...
  • 您可能想阅读此article by Steve Myers about encapsulation 和此GotW by Herb Sutter about std::string。基本上,您可以在其直接接口之外实现的类功能越多越好。
  • @Theolodis 不过没必要这么做,否则利大于弊,界面臃肿。如果 std::find 能够专门为快速查找容器做正确的事情,那就更好了。
  • 你搞错了。 <algorithm> 的版本应该是您的默认值。只有在有特殊要求时才有特殊的成员函数。

标签: c++ stl containers


【解决方案1】:

因为 algorithm 中的 std::find 函数适用于他们。

一般来说,std::vectorstd::list 这样的容器具有线性搜索时间复杂度。因此,将成员 find 附加到它们是一种冗余,因为已经存在 std::find。对于其他容器(例如,std::setstd::map 等),有更好的方法(即比线性复杂度更快)来实现搜索。因此,实现者将这些更快的搜索算法实现为成员函数。

【讨论】:

  • OP 知道这一点“我知道还有使用 std::find 的替代方法”
  • 即使这在技术上是正确的,也不能真正回答问题。
  • @MatthiasB 他正在使用“脚踏实地”声誉收集技术.. 快速发布一条答案以获得一些赞成票,然后再写下详细信息。 现在它回答了这个问题。
  • 既然这个答案回答了这个问题,如果反对者解释他们反对的原因(如果有的话......)会很好。
【解决方案2】:

具有类似按键搜索功能的容器将集成 find 方法(例如,map 在内部使用可以高效查找的二叉树实现)。

其他的,比如你引用的那些,将允许使用 std::find 进行范围搜索,但没有特色查找功能,因为它与 std::find 相比没有算法优势 (排序/特殊情况除外)

【讨论】:

  • 在我看来,这并没有真正回答这个问题。答案可能在于规格......
  • @Theolodis 主要原因刚刚被加粗了
【解决方案3】:

一般的设计原则是尽可能使用std::find,在效率更高的时候实现find成员函数。

find成员的容器是具有比std::find中执行的线性搜索更有效的元素查找机制的容器。例如,二叉搜索树(如 std::setstd::map)或哈希表(如 unordered 对应物)。

【讨论】:

    【解决方案4】:

    对不同的容器使用相同的函数可以使 API 更清晰,您不必了解每个容器的特性,只需了解如何应用一个函数用于所有容器。

    这也是为了代码的可重用性 - 您使用从任何提供迭代器的容器中获取迭代器的算法,因此该算法不必依赖于 std::vectorstd::list 等容器。

    【讨论】:

      【解决方案5】:

      std::vectorstd::liststd::forward_list 等容器是顺序容器。没有什么比顺序搜索更能应用于这些容器了。因此,如果所有这些容器都相同,则无需重写每个顺序容器的顺序搜索。

      例外是 std::basic_string 类,它最初模拟已经具有特殊搜索功能的 C 字符串,如 strchr、strstr 等。

      【讨论】:

        【解决方案6】:

        findlower_boundupper_bound 成员函数仅在比使用非成员等效函数更有效 时提供,或者当 非成员不能操作给定容器的公共 API

        特别注意std::string 有一个find 函数,它为字符搜索提供std::find()-like 线性搜索工具和为子字符串搜索提供std::search()-like 工具:而非成员版本可能有相同的 big-O 效率,考虑到专用机器代码指令通常可用于“字符串”搜索,它们的效率可能会降低。还有历史、方便和易于移植的因素。

        除了效率问题,值得注意的是一些容器:

        • 本质上要么已排序(multi-setmap),要么未排序(unordered_mapunordered_set),通常未排序(例如std::string),或者很容易(@987654334) @)

        • 公开支持前向迭代和/或随机访问

        • 可能私下支持二分查找

        • 拥有如此专门的公共 API 用于元素访问,该算法的潜在重用相对有限(例如 unordered_map::bucket / ::begin(n) 等)

        有趣的是,vector 中的搜索可以使用多种算法完成:

        • std::find 进行强力线性 O(n) 搜索,首先“找到”低索引元素,

        • std::binary_search 需要一个已排序的向量,但会跳转以实现 O(log2n) 复杂度。

        • 外推搜索和散列等其他选项可能适用

        您会如何选择要实施并添加为成员?似乎有点随意。尽管如此,选择使用哪个元素在性能方面可能很重要:对于一百万个元素,find 平均在匹配前进行 50 万个元素比较,而当元素不存在时则为整百万个元素比较,而 binary_search 通常需要大约 20 次比较。

        带有find 的容器通常不提供这种灵活性,而它们提供的find 和/或lower_bound/upper_bound 可以被视为非成员等价物的替代品,并且可能是唯一的搜索容器的合理方法。

        【讨论】:

          【解决方案7】:

          正如其他 cmets 所述,设计原理是 vector::find() 可以像非成员函数 std::find() 一样高效地实现。使用后者的好处是它解耦了数据结构和作用于数据结构的操作符,从而提高了可维护性(这对库的开发人员来说是有利的)。

          但是,前者的好处是它可以使所有容器之间的 API 保持一致,并使客户端代码不那么冗长。这将增加可学习性和可读性(这对图书馆的用户来说是有利的)。此外,一致的 API 将允许编写通用代码。考虑一下:

          template <typename Container, typename T>
          void foo(const Container& c, const T& x) {
              if (std::find(c.begin(), c.end(), x) != c.end()) {
                  // ...
              }
          }
          

          Containerstd::mapstd::set 时,上述方法效率低下。为了提高效率,我们需要这样做:

          template <typename Container, typename T>
          void foo(const Container& c, const T& x) {
              if (c.find(x) != c.end()) {
                  // ...
              }
          }
          

          但是它不能为std::vectorstd::list 编译。这给库的用户增加了为他们想要支持的每种类型手动专门化/重载的通用函数的负担:

          template <typename T>
          bool contains(const std::vector<T>& c, const T& x) {
              return std::find(c.begin(), c.end(), x) != c.end();
          }
          
          template <typename T>
          bool contains(const std::set<T>& c, const T& x) {
              return c.find(x) != c.end();
          }
          
          template <typename Container, typename T>
          void foo(const Container& c, const T& x) {
              if (contains(c, x)) {
                  // ...
              }
          }
          

          我承认做出这些类型的设计决策很困难,但我认为 STL 的设计者在这里犯了一个错误。非常小的可维护性负担似乎在很大程度上值得为用户提供更好的 API 和一致性。简而言之,由于find 必须是某些容器的成员函数(为了性能),那么find 应该是所有容器的成员函数(为了一致性)。请注意,我完全同意其他算法是非成员函数。

          (我的意思是,来吧,根据定义,容器是包含东西的东西。用户编写一个通用且高效的“包含”函数应该是微不足道的。事实上,我认为它应该是添加到Container 概念,但我离题了。)

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-10-16
            • 2020-05-19
            • 1970-01-01
            • 2012-04-10
            • 2010-11-06
            相关资源
            最近更新 更多