【问题标题】:Is this code an abuse of STL's find_if?这段代码是滥用 STL 的 find_if 吗?
【发布时间】:2010-09-06 20:33:24
【问题描述】:

假设我有一个存储在向量中的服务器名称列表,我想一次与他们联系,直到他们成功响应。我正在考虑通过以下方式使用 STL 的 find_if 算法:

find_if(serverNames.begin(), serverNames.end(), ContactServer());

ContactServer 是一个谓词函数对象。
一方面,存在一个问题,因为谓词不会总是为相同的服务器名称返回相同的结果(因为服务器停机时间、网络问题等)。但是,无论使用哪个谓词副本,都将返回相同的结果(谓词没有实际状态),因此在这种情况下,与状态保持谓词的原始问题无关。

你说什么?

【问题讨论】:

    标签: c++ stl


    【解决方案1】:

    我想我会去的。

    我唯一担心的是它的可读性(以及可维护性)。对我来说,它的内容类似于“找到我可以联系的第一台服务器”,这非常有意义。

    您可能想要重命名ContactServer 以表明它是一个谓词; CanContactServer? (但后来人们会抱怨隐藏的副作用。嗯...)

    【讨论】:

      【解决方案2】:

      这正是 STL 算法的用途。这根本不是滥用。此外,它的可读性很强。重定向到 null 任何告诉你的人。

      【讨论】:

      • 正如我所想,STL 算法不仅适用于整数容器。
      【解决方案3】:

      在我看来,std::find_if 的这种用法有点误导。当我阅读这段代码时,我不希望出现任何副作用,我只是希望找到一个服务器名称。 find_if 的结果被丢弃的事实也让我怀疑代码是否真的正确。也许谓词的另一个名称会使意图更清晰,但我认为问题更根本。

      对于大多数人来说,find_if 是一种查询算法,而不是修改算法。即使您实际上并未修改迭代的值,但您正在修改应用程序的全局状态(在这种情况下,您甚至可能修改远程服务器的状态)。

      在这种情况下,我可能会坚持使用手动循环,尤其是现在 C++11 引入了基于范围的 for 循环:

      for (std::string const & name : serverNames)
      {
          if (ContactServer(name)) break;
      }
      

      另一种解决方案是将其封装在一个函数中,其名称可以更清楚地传达意图,例如 apply_until 或类似的名称:

      template <typename InputIterator, typename Function>
      void apply_until(InputIterator first, InputIterator last, Function f)
      {
          std::find_if(first, last, f);
          // or
          // while (first != last)
          // {
          //     if (f(*first)) break;
          //
          //     ++first;
          // }
      }
      }
      

      但也许我过于纯粹了:)!

      【讨论】:

        【解决方案4】:

        这不是find_if 的用途吗?

        但请注意,如果您遍历迭代器,它将找到 所有 服务器 - 但您不会这样做(根据 OP)。

        【讨论】:

          【解决方案5】:

          但是,无论使用哪个谓词副本(即谓词没有真实状态),都将返回相同的结果,因此在这种情况下,与状态保持谓词的原始问题无关。

          那么问题出在哪里?函数对象不一定是有状态的。在这种情况下使用函数对象而不是函数指针实际上是最佳实践,因为编译器更擅长内联它们。在您的情况下,函数对象的实例化和调用可能根本没有开销,因为find_if 是一个函数模板,编译器将为您的仿函数生成一个自己的版本。

          另一方面,使用函数指针会导致间接。

          【讨论】:

            【解决方案6】:

            upcoming version of the C++ standard 中,对于谓词应始终为相同的输入返回相同的值这一事实,我找不到任何明确的限制。我查看了第 25 节(第 7 至 10 段)。

            返回值的方法可能会从一个调用更改为另一个调用,如您的情况,应该是 volatile (从 7.1.6.1/11 开始:“volatile 是对实现的提示,以避免涉及对象的激进优化,因为对象可能会通过实现无法检测到的方式进行更改”)。

            谓词“不应通过取消引用的迭代器应用任何非常量函数”(第 7 和 8 段)。我认为这意味着它们不需要使用非易失性方法,因此您的用例就标准而言是可以的。

            如果措辞是“谓词应该应用 const 函数......”或类似的东西,那么我会得出结论,'const volatile' 函数是不行的。但这种情况并非如此。

            【讨论】:

              【解决方案7】:

              std::for_each 可能是更好的选择。

              1) 在被复制到同一个函数对象后,在每个元素上使用,并且在处理完所有元素后,将可能更新的函数对象的副本返回给用户。

              2) 在我看来,这也会提高调用的可读性。

              函数对象和 for_each 调用如下所示:

              
              struct AttemptServerContact {
                bool        server_contacted;
                std::string active_server;    // name of server contacted
              
                AttemptServerContact() : server_contacted(false) {}
              
                void operator()(Server& s) {
                  if (!server_contacted) {
                    //attempt to contact s
                    //if successful, set server_contacted and active_server
                  }
                }
              };
              
              AttemptServerContact func;
              func = std::for_each(serverNames.begin(), serverNames.end(), func);
              //func.server_contacted and func.active_server contain server information.
              

              【讨论】:

              • for_each() 不满足问题的这个要求:I would like to contact them one at a time until one has successfully responded。这将继续并联系容器中的所有可联系服务器。然后在最后,用户只得到最后一个可联系的人的信息,而不是第一个,或者至少是所有的......不是他们似乎想要任何信息。
              【解决方案8】:

              find_if 在这里似乎是正确的选择。在这种情况下,谓词是无状态的。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2011-04-07
                • 1970-01-01
                • 1970-01-01
                • 2011-01-14
                • 1970-01-01
                • 2012-06-13
                • 2010-12-04
                • 1970-01-01
                相关资源
                最近更新 更多