我应该使用std :: for_each吗?

时间:2011-06-07 04:29:38

标签: c++ stl lambda for-loop foreach

我总是试图更多地了解我使用的语言(不同的样式,框架,模式等)。我注意到我从不使用std::for_each所以我想也许我应该开始。在这种情况下,目标是扩展我的思维,并且以某种方式改进代码(可读性,表现力,紧凑性等)。

因此,考虑到上下文,最好将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; }

我希望这个问题看起来毫无意义。我想它几乎会提出一个更大的问题......如果一个中级程序员使用语言功能,即使他真的不需要这次,只是为了让他能更好地理解这个功能。实际上可能从中获益的时间。虽然可能已经提出了这个更大的问题(例如here)。

9 个答案:

答案 0 :(得分:46)

使用std::for_each而不是旧学校for循环(甚至是新奇的C ++ 0x范围 - for循环)是有利的:你可以看一下声明中的一句话,你确切地知道该声明的作用。

当您看到for_each时,您知道lambda中的操作只对该范围中的每个元素执行一次(假设没有抛出异常)。在处理每个元素之前不可能在循环之前中断循环,并且不可能跳过元素或多次为一个元素计算循环体。

使用for循环,您必须阅读循环的整个主体才能知道它的作用。它中可能包含continuebreakreturn语句,这些语句会改变控制流。它可能包含修改迭代器或索引变量的语句。没有检查整个循环就没有办法知道。

Herb Sutter讨论了使用算法和lambda表达式in a recent presentation to the Northwest C++ Users Group的优点。

请注意,如果您愿意,可以在此处使用std::copy算法:

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

答案 1 :(得分:24)

取决于。

for_each的强大之处在于,您可以将其与任何其迭代器满足输入迭代器概念的容器一起使用,因此它通常可用于任何容器。这样可以提高可维护性,只需更换容器即可,无需更改任何内容。对于向量的size上的循环,情况也是如此。您可以在不必更改循环的情况下交换它的唯一其他容器将是另一个随机访问的容器。

现在,如果您自己输入迭代器版本,典型版本如下所示:

// 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可能是某些详细类型,如my_type<int>::nested_type。虽然,可以typedef他/她的方式。老实说,我仍然不明白为什么lambda不允许带类型演绎的多态...


现在,需要考虑的另一件事是for_each,名称本身已经表达了意图。它表示序列中不会跳过任何元素,这可能是你的正常for循环的情况。

这让我想到了另一点:由于for_each旨在在整个序列上运行并对容器中的每个项应用操作,因此它不能用于处理早期{ {1}}或return一般。可以使用lambda / functor中的break语句模拟continue

因此,使用return 想要在集合中的每个项目上应用操作。

另一方面,for_each可能只是因为基于范围的for循环(也称为foreach循环)而被C ++ 0x“弃用”:

for_each

哪个更短(yay)并且允许以下三个选项:

  • 早退(即使有返回值!)
  • 打破循环和
  • 跳过某些元素。

答案 2 :(得分:9)

我通常会建议使用std::for_each。您的循环示例不适用于非随机访问容器。您可以使用迭代器编写相同的循环,但由于写出std::SomeContainerName<SomeReallyLongUserType>::const_iterator作为迭代变量的类型,通常会很痛苦。 std::for_each会将您与此隔离开来,并自动将呼叫分摊到end

答案 3 :(得分:8)

恕我直言,您应该在测试代码中尝试这些新功能。

生产代码中,您应该尝试使用您认为合适的功能。 (即如果您对for_each感到满意,可以使用它。)

答案 4 :(得分:3)

for_each是迭代序列的最常用算法,因此表达最少。如果迭代的目标可以用transformaccumulatecopy表示,我觉得使用特定算法而不是通用for_each会更好。

使用新的C ++ 0x范围(在gcc 4.6.0中支持,尝试一下!),for_each甚至可能失去其作为将序列应用于函数的最通用方法的利基。

答案 5 :(得分:1)

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

例如:

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

其中T是您想要的每种类型。

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

答案 6 :(得分:0)

嗯......这有效,但是对于打印矢量(或其他容器类型的内容),我更喜欢这个:

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

答案 7 :(得分:0)

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

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

(或其他答案中建议的带有ostream迭代器的boost::copy)。

答案 8 :(得分:0)

请注意,“传统”示例是错误的:

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

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

一个是int的排名可能低于std::vector<T>::size_type。在32位机器上,int通常为32位宽,v.size()几乎肯定是64位宽。如果你设法将2 ^ 32个元素填充到向量中,那么你的索引永远不会到达终点。

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

您可能事先知道,对于此向量,这些错误条件永远不会成立。但您必须忽略或禁用编译器警告。如果您禁用它们,那么您无法获得这些警告的好处,从而帮助您在代码中的其他位置找到实际的错误。 (我花了很多时间来跟踪这些编译器警告应该检测到的错误,如果代码已经可以启用它们的话。)

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

的基于迭代器的循环

使用<algorithm>或迭代器而不是索引的另一个好处是,它可以让您更灵活地在将来更改容器类型,而无需重构使用它的所有代码。