【问题标题】:Why does std::for_each(from, to, function) return function?为什么 std::for_each(from, to, function) 返回函数?
【发布时间】:2011-01-04 04:09:00
【问题描述】:

我刚刚看了std::for_each的代码:

template<class InputIterator, class Function>
Function for_each(InputIterator first, InputIterator last, Function f)
{
  for ( ; first!=last; ++first ) f(*first);
  return f;
}

并且看不出这个模板函数返回输入函数的任何充分理由。有没有人有任何关于这在哪里有用的例子?

【问题讨论】:

    标签: c++ templates stl


    【解决方案1】:

    这是为了让您在函数中累积状态,然后将其返回给您的调用代码。例如,您的函数(作为仿函数类)可以有一个成员 int 用于计算它被调用的次数。

    这是一个包含一些示例的页面:https://web.archive.org/web/20171127171924/http://xenon.arcticus.com/c-morsels-std-for-each-functors-member-variables

    【讨论】:

    • 曾有人告诉我(我认为是查尔斯·贝利),这种行为并不能保证。我不相信他;我认为该标准虽然不清楚,但在这种情况下大多是有意义的。 (为什么我们会得到一个函数?)但这只是一个警告,也许这只是“标准”编译器行为而不是标准行为。
    • @GMan:标准要求返回函数(§25.1.1/2)。
    • 不要忘记&lt;numeric&gt;中的std::accumulate,在这种情况下,它允许您在函子之外维护状态。
    • FWIW 符合 c++11 的库将根据 §25.2.4/3 返回 std::move(f) ,因此您可以保证能够从外部读取变异状态。
    • 链接已损坏。
    【解决方案2】:

    可能是 Alex Stepanov 具有函数式编程范式,但您会发现 std::accumulatestd::for_each 都通过值而不是通过引用传递它们的操作数(函数和累加值)。因此:

    class MyFunctor
    {
       Y val;
       public:
         MyFunctor() : val() {}
    
         void operator()( X const& x )
         {
            // do something to modify val based on x
         }
    
         Y getValue() const { return val; }   
    };
    

    现在如果你尝试:

    MyFunctor f;
    for_each( coll.begin(), coll.end(), f );
    Y y = f.getValue();
    

    它不起作用,因为for_each 一直在处理f 的副本。当然,您可以在内部拥有一个 shared_ptr&lt;Y&gt; 的实例,因此它将指向同一个实例。您还可以将 MyFunctor 中的 val 设为引用,在循环外创建它并将其传递给 MyFunctor。

    但是该语言允许您这样做:

    Y y = for_each( coll.begin(), coll.end(), MyFunctor() ).getValue();
    

    美观又方便,一站式服务。

    要对std::accumulate 做同样的事情,可以这样:

    class MyFunctor2
    {
    public:
          Y operator()( Y y, X const& x ) const
          {
             //    create a new Y based on the old one and x
            ...
          }
    };
    
    Y y = std::accumulate( coll.begin(), coll.end(), Y(), MyFunctor2() );
    

    您可以使用函数(或在 C++11 中为 lambda)而不是仿函数。请注意,这里的仿函数没有状态,并且您将初始化的对象作为参数传入,这可以是临时的。

    现在我们知道 Y 是可复制的。 std::accumulate 在 Y 上使用 by value,而不是就地修改。顺便说一句,当就地修改确实更有效时,有一种解决方法,无需编写新算法(例如使用 += 或引用修改的累积 2),方法是使用以下函数签名:

    Y * func( Y* py, X const & ); // function modifies *py in-place then returns py
    

    然后调用:

    Y y;
    std::accumulate( coll.begin(), coll.end(), &y, func );
    

    我们“知道”返回值将是 &y。如果我们想在一个地方访问 Y 的成员,我们可以利用它,例如

    Y y;
    Z z = std::accumulate( coll.begin(), coll.end(), &y, func )->getZ();
    

    顺便说一句,for_each 中的副本和 accumulate 中的副本的一个关键区别在于它将复制的复杂性/数量。使用for_each,您的仿函数最多将复制2份:一份作为函数的参数,一份作为返回值。我说“最多”是因为返回值优化可以减少这些副本中的第二个。使用accumulate,它会复制集合中的每个元素,即O(N),而不是固定时间。因此,如果副本稍微昂贵,则仿函数中的双重副本不会是在大型集合上迭代少量次的主要费用,而对于累积它会是(并且建议是指针破解)。

    【讨论】:

    • 与其传递一个指针来累积,使用std::reference_wrapper(并转换为参数列表中的引用类型)可能会更好。毕竟,reference_wrapper 的一个主要用例是不应该被复制的函子,所以除了支持不使用指针的风格论点之外,将它应用于状态也是有意义的。话虽如此 - 也许在那个阶段,我们也可以使用 for[_each] 并通过引用捕获,因为我们基本上是在欺骗 accumulate 使其具有不同的语义,这似乎可能会破坏这一点。
    • 另外,也许我很密集,但你为什么说accumulate() 会复制每个元素的函子?我无法想象为什么这是必要的或实际完成的。示例实现on cppreference 也没有显示这样的东西;函子按值取值,然后调用 N 次,但不再复制。
    【解决方案3】:

    返回函数基本上让std::for_each变成了std::accumulate的平庸模仿。它允许您在函数/函子中累积一些东西,然后在完成后检索该累积值。几乎任何时候您认为这可能有用,您可能应该考虑改用std::accumulate

    【讨论】:

    • 其实 std::accumulate 很笨拙。
    【解决方案4】:

    如果你传入一个函数对象,也就是函子,并且它有状态,返回函数对象允许你在迭代序列后访问它的状态。假设您有一个函数对象,它从序列中计算三个不同的变量并将它们保存在成员变量中。每次调用仿函数时,都会更新计数器。如果for_each 没有返回对象,你将如何得到结果?

    注意...这就是为什么您必须始终实现复制构造,并为具有状态的函数对象赋值。

    【讨论】:

    • 你不必为仿函数实现复制构造函数或赋值运算符,就像你为任何其他类所做的一样——默认值很可能会做必要的事情。
    【解决方案5】:

    如果您想在调用之间保存(并稍后使用)函子状态,它很有用,例如,计算集合中的元素数量或通过设置一些内部变量来指示处理元素的某些失败。

    【讨论】:

      【解决方案6】:

      我猜没有特别的原因。您可以做的是在另一个 foreach 调用中使用返回的函数,从而避免两次写入函数名并可能在那里出错。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-09-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多