我应该使用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
循环,您必须阅读整个循环体来知道它的作用。 它可能会continue
, break
或return
语句来改变控制stream。 它可能有修改迭代器或索引variables的语句。 没有检查整个循环就没有办法知道。
Herb Sutter 在最近向西北C ++用户组发表演讲中讨论了使用algorithm和lambdaexpression式的优势。
请注意,如果您愿意,您可以在这里实际使用std::copy
algorithm:
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。 如果迭代的目标可以用transform
, accumulate
, copy
来表示,我觉得使用特定的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的时候有更大的灵活性,而不用重构所有使用它的代码。