为什么std::for_each(from, to, function)返回function?

38

我刚刚阅读了 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;
}

并且我没有看到这个模板函数返回输入函数的好理由。有人有任何关于在哪里使用它会很有用的例子吗?

7个回答

48

4
@GMan:按照标准(§25.1.1/2),需要返回该函数。 - Jerry Coffin
1
不要忘记在<numeric>中使用std::accumulate,它允许你在这种情况下在函数对象外维护状态。 - Potatoswatter
4
就我所知,符合C++11标准的库会根据§25.2.4/3返回std::move(f),因此您可以确保能够在外部读取已变异的状态。 - boycy
@GMan:通常最好将任何需要维护一些状态的函数对象成员变量存储为引用而不是副本。这是因为这些算法通常会复制函数对象并将其应用于副本,而不是原始对象(除非您使用boost::ref对其进行了包装,这将使其按引用传递)。 - Ben J
6
链接已损坏。 - Daniel Ryan
显示剩余3条评论

13

可能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<Y>实例,因此指向同一实例。您还可以将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表达式)代替functor。请注意,这里的functor没有状态,你将初始化的对象作为参数传递进去,它可以是一个临时变量。

现在我们知道Y是可复制的。std::accumulate对Y使用按值传递,而不是就地修改。顺便说一句,当就地修改确实更有效率时,有一种解决方法可以避免编写新算法(例如使用+=或引用修改的accumulate2),那就是使用以下函数签名:

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_eachaccumulate的复制中,一个关键区别是它们所要进行的复制的复杂度或数量。在for_each中,最多会有两个副本被创建:一个作为函数参数,另一个作为返回值。我说“最多”的原因是,返回值优化可能会减少这两个副本中的第二个。而在accumulate中,它会将每个元素都进行复制,即O(N)而不是常数时间。因此,如果复制相对昂贵,那么在大型集合上迭代少量次数时,functor中的双重复制不会成为主要开销,而对于累加器(accumulate)而言,则会导致较大的开销(建议使用指针技巧)。


与其传递一个累加器指针,使用std::reference_wrapper(并在参数列表中转换为引用类型)可能更好。毕竟,reference_wrapper的主要用例之一是不能被复制的函数对象,因此除了不使用指针的风格论点外,将其应用于状态也是有意义的。话虽如此 - 也许在那个阶段,我们最好使用for[_each]并通过引用捕获,因为我们基本上是欺骗accumulate具有不同的语义,这似乎可能会失去重点。 - underscore_d
1
也许我有些迟钝,但是为什么你会说 accumulate() 每次元素操作都会复制函数对象?我无法想象这会有何必要或实际可行性。cppreference上的示例实现也没有表明这一点;函数对象被按值传递,然后被调用 N 次,但从未被再次复制。 - underscore_d

3
如果你传入一个有状态的函数对象(也称为functor),并且在迭代序列后要访问它的状态,返回该函数对象将允许你访问其状态。假设你有一个函数对象,它从序列计算三个不同的变量并将它们保存在成员变量中。每次调用函数对象时,你都会更新计数器。如果for_each没有返回该对象,你如何获得结果?
注意...这就是为什么你必须始终为具有状态的函数对象实现复制构造和赋值的原因。

3
对于一个函数对象,你不需要像其他类一样实现拷贝构造函数和赋值运算符,因为默认的行为很可能已经满足需求了。 - anon

3

将函数返回基本上使std::for_each成为std::accumulate的平庸模仿。它允许您在函数/函数对象中累加某些内容,然后在完成时检索累加值。几乎任何时候,当您认为这可能是有用的时候,您都应该考虑使用std::accumulate


实际上,std::accumulate非常笨重。 - CashCow

2

如果您想在调用之间保存(并稍后使用)函数对象的状态,例如计算集合中元素的数量或通过设置一些内部变量来指示无法处理元素,则此功能非常有用。


0

添加一个示例以展示从for_each返回函数对象的有用性(在迭代后保留状态)。

struct Accumulator {
    int counter = 0;
    int operator()(int i) { return counter += i; } 
};

int main()
{
    std::vector<int> vec{ 1,2,3 };

    Accumulator acc;
    std::for_each(vec.begin(), vec.end(), acc); // counter value will be lost
    cout << acc.counter;    // 0

    Accumulator sumObj = std::for_each(vec.begin(), vec.end(), acc); // counter value preserved
    cout << acc.counter;    // 0
    cout << sumObj.counter; // 6  = sum of all vector elements
}

-5

没有特别的原因吧。不过你可以在另一个foreach调用中使用返回的函数,这样就避免了两次写函数名可能出现的错误。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接