我应该使用std :: for_each吗?

我总是试图更多地了解我使用的语言(不同的风格,框架,模式等)。 我注意到,我从来没有使用std::for_each所以我想也许我应该开始。 在这种情况下的目标是扩大我的想法,而不是在某种程度上改进代码(可读性,performance力,紧凑性等)。

因此,考虑到这种情况,使用std::for_each进行简单的任务(比如打印出一个向量)是一个好主意:

 for_each(v.begin(), v.end(), [](int n) { cout << n << endl; } 

[](int n)是一个lambda函数)。 代替:

 for(int i=0; i<v.size(); i++) { cout << v[i] << endl; } 

我希望这个问题似乎没有意义。 我想这几乎要问一个更大的问题了……如果一个中级程序员使用语言function,尽pipe他现在不需要,但是只是为了让他能更好地理解这个function,它。 虽然这个更大的问题可能已经被问到(例如这里 )。

使用std::for_each而不是旧for循环(甚至是新的C ++ 0x范围for循环)有一个好处:你可以看看语句的第一个单词,你确切地知道语句的作用。

当你看到for_each ,你知道lambda中的操作对于范围中的每个元素只执行一次(假定没有抛出exception)。 在处理每个元素之前不可能跳出循环,并且不可能多次跳过元素或评估一个元素的循环体。

使用for循环,您必须阅读整个循环体来知道它的作用。 它可能会continuebreakreturn语句来改变控制stream。 它可能有修改迭代器或索引variables的语句。 没有检查整个循环就没有办法知道。

Herb Sutter 在最近向西北C ++用户组发表演讲中讨论了使用algorithm和lambdaexpression式的优势。

请注意,如果您愿意,您可以在这里实际使用std::copyalgorithm:

 std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, "\n")); 

这取决于。

for_each的强大之处在于,您可以将它用于迭代器满足input迭代器概念的任何容器,因此它可以在任何容器上使用。 这增加了可维护性,您可以换出容器而不需要更改任何内容。 对于遍历vectorsize的循环,情况也是如此。 唯一的其他容器,你可以交换,而不必改变循环将是另一个随机访问。

现在,如果你自己input迭代器版本,典型的版本如下所示:

 // substitute 'container' with a container of your choice for(std::container<T>::iterator it = c.begin(); it != c.end(); ++it){ // .... } 

很长,呃? C ++ 0x使用auto关键字解决了这个问题:

 for(auto it = c.begin(); it != c.end(); ++it){ // .... } 

已经更好,但仍然不完美。 每一次迭代你都要end ,这可以做得更好:

 for(auto it = c.begin(), ite = c.end(); it != ite; ++it){ // .... } 

现在看起来不错。 不过,比同等版本的for_each版本更长:

 std::for_each(c.begin(), c.end(), [&](T& item){ // ... }); 

对于“等价”稍微主观,因为lambda的参数列表中的T可能是一些冗长的types,如my_type<int>::nested_type 。 尽pipe如此,人们还是可以typedef 。 老实说,我还是不明白为什么lambda不被允许多态与types扣除…


另外要考虑的是, for_each这个名字本身已经expression了一个意图。 它说,序列中没有元素会被跳过,这可能是你正常的for循环的情况。

这使我for_each另一点:因为for_each是为了遍历整个序列并对容器中的每个项目执行一个操作,所以它并不是为了处理早期return或总体上的break而devise的。 continue可以用lambda / functor的return语句来模拟。

所以,使用for_each真的想在集合中的每个项目上应用一个操作。

在附注中, for_each可能仅仅因为基于范围的for循环(也称为foreach循环)而被C ++ 0x“弃用”:

 for(auto& item : container){ // ... } 

哪个方法更短(耶),并允许所有三个选项:

  • 提前返回(即使有返回值!)
  • 打破循环和
  • 跳过一些元素。

我一般会build议使用std::for_each 。 您的循环示例不适用于非随机访问容器。 你可以使用迭代器编写相同的循环,但是由于写出std::SomeContainerName<SomeReallyLongUserType>::const_iterator作为迭代variables的types通常是一个痛苦。 std::for_each将你从这里隔离开来,并且把这个调用分解为自动end

恕我直言,你应该在你的testing代码中尝试这个新function。

生产代码中,您应该尝试您感觉舒适的function。 (也就是说,如果你对for_each感到满意,你可以使用它。)

for_each是在一个序列上迭代的最一般的algorithm,因此expression最less。 如果迭代的目标可以用transformaccumulatecopy来表示,我觉得使用特定的algorithm比使用通用的for_each更好。

使用新的C ++ 0x范围(在gcc 4.6.0中支持,试一试!), for_each可能甚至失去了将函数应用到序列的最通用的方式。

您可以使用for循环范围C ++ 11

例如:

  T arr[5]; for (T & x : arr) //use reference if you want write data { //something stuff... } 

T是你想要的每一种types。

它适用于STL和经典数组中的每个容器。

嗯…这是有效的,但打印vector(或其他容器types的内容),我更喜欢这个:

 std::copy(v.begin(), v.end(), std::ostream_iterator< int >( std::cout, " " ) ); 

Boost.Range简化了标准algorithm的使用。 对于你的例子,你可以写:

 boost::for_each(v, [](int n) { cout << n << endl; }); 

(或按照其他答案中的build议,使用ostream迭代器进行boost::copy )。

请注意,“传统”的例子是越野车:

 for(int i=0; i<v.size(); i++) { cout << v[i] << endl; } 

这假定int可以始终表示向量中每个值的索引。 实际上有两种方法可能出错。

一个是int可能比std::vector<T>::size_type 。 在32位机上, int s通常是32位宽,但v.size()几乎肯定是64位宽。 如果您设法将2 ^ 32元素填充到vector中,那么您的索引将永远不会达到结尾。

第二个问题是你正在比较一个有符号值( int )和一个无符号值( std::vector<T>::size_type )。 因此,即使它们的级别相同,当大小超过最大整数值时,索引也会溢出并触发未定义的行为。

你可能事先知道, 对于这个向量 ,这些错误条件永远不会是真的。 但是,你必须忽略或禁用编译器警告。 如果你禁用了它们,那么你就不能从这些警告中获益,从而帮助你在代码的其他地方find实际的错误。 (我花了很多时间来追踪这些编译器警告应该检测到的错误,如果代码使得它们可行的话)。

所以,是的, for_each (或任何适当的<algorithm> )更好,因为它避免了这种有害的滥用int 。 您也可以使用基于范围的for循环或基于迭代器的循环。

使用<algorithm>或迭代器而不是索引的另外一个好处是它可以让你在将来更改容器types的时候有更大的灵活性,而不用重构所有使用它的代码。