C++ for 循环 vs std::for_each 与 lambda
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11956374/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
for loop vs std::for_each with lambda
提问by Andrey
Let's consider a template function written in C++11 which iterates over a container. Please exclude from consideration the range loop syntax because it is not yet supported by the compiler I'm working with.
让我们考虑一个用 C++11 编写的模板函数,它迭代容器。请排除范围循环语法,因为我正在使用的编译器尚不支持它。
template <typename Container>
void DoSomething(const Container& i_container)
{
// Option #1
for (auto it = std::begin(i_container); it != std::end(i_container); ++it)
{
// do something with *it
}
// Option #2
std::for_each(std::begin(i_container), std::end(i_container),
[] (typename Container::const_reference element)
{
// do something with element
});
}
What are pros/cons of for loop vs std::for_each
in terms of:
for 循环与以下std::for_each
方面的优缺点是什么:
a) performance? (I don't expect any difference)
一场表演?(我不希望有任何区别)
b) readability and maintainability?
b) 可读性和可维护性?
Here I see many disadvantages of for_each
. It wouldn't accept a c-style array while the loop would. The declaration of the lambda formal parameter is so verbose, not possible to use auto
there. It is not possible to break out of for_each
.
在这里,我看到了许多缺点for_each
。它不会接受 c 风格的数组,而循环会。lambda 形式参数的声明非常冗长,无法在auto
那里使用。突破是不可能的for_each
。
In pre- C++11 days arguments against for
were a need of specifying the type for the iterator (doesn't hold any more) and an easy possibility of mistyping the loop condition (I've never done such mistake in 10 years).
在 C++11 之前的日子里,反对的论点for
是需要指定迭代器的类型(不再成立)和错误输入循环条件的容易可能性(我 10 年来从未犯过这样的错误)。
As a conclusion, my thoughts about for_each
contradict the common opinion. What am I missing here?
作为结论,我的想法for_each
与普遍意见相矛盾。我在这里缺少什么?
采纳答案by Walter
I think there are some other differences not yet covered by the answers so far.
我认为到目前为止答案还没有涵盖其他一些差异。
a
for_each
can accept anyappropriate callable object, allowing one to 'recycle' the loop body for different for loops. For example (pseudo code)for( range_1 ) { lengthy_loop_body } // many lines of code for( range_2 ) { lengthy_loop_body } // the same many lines of code again
becomes
auto loop_body = some_lambda; // many lines of code here only std::for_each( range_1 , loop_body ); // a single line of code std::for_each( range_2 , loop_body ); // another single line of code
thus avoiding duplication and simplifying code maintenance. (Of course, in a funny mix of styles one could also use a similar approach with the
for
loop.)another difference regards breaking out of the loop (with
break
orreturn
in thefor
loop). As far as I know, in anfor_each
loop this can only be done by throwing an exception. For examplefor( range ) { some code; if(condition_1) return x; // or break more code; if(condition_2) continue; yet more code; }
becomes
try { std::for_each( range , [] (const_reference x) { some code; if(condition_1) throw x; more code; if(condition_2) return; yet more code; } ); } catch(const_reference r) { return r; }
with the same effects regarding calling of destructors for objects with scope of the loop body and the function body (around the loop).
the main benefit of
for_each
is, IMHO, that one can overload it for certain container types, when plain iteration is not as efficient. For example, consider a container that holds a linked list of data blocks, each block containing a contiguous array of elements, similar to (omitting irrelevant code)namespace my { template<typename data_type, unsigned block_size> struct Container { struct block { const block*NEXT; data_type DATA[block_size]; block() : NEXT(0) {} } *HEAD; }; }
then an appropriate forward iterator for this type would require to check for the end of block at each increment and the comparison operator needs to compare both the block pointer and the index within each block (omitting irrelevant code):
namespace my { template<typename data_type, unsigned block_size> struct Container { struct iterator { const block*B; unsigned I; iterator() = default; iterator&operator=(iterator const&) = default; iterator(const block*b, unsigned i) : B(b), I(i) {} iterator& operator++() { if(++I==block_size) { B=B->NEXT; I=0; } // one comparison and branch return*this; } bool operator==(const iterator&i) const { return B==i.B && I==i.I; } // one or two comparisons bool operator!=(const iterator&i) const { return B!=i.B || I!=i.I; } // one or two comparisons const data_type& operator*() const { return B->DATA[I]; } }; iterator begin() const { return iterator(HEAD,0); } iterator end() const { return iterator(0,0); } }; }
this type of iterator works correctly with
for
andfor_each
, for examplemy::Container<int,5> C; for(auto i=C.begin(); i!=C.end(); // one or two comparisons here ++i) // one comparison here and a branch f(*i);
but requires two to three comparisons per iteration as well as a branch. A more efficient way is to overload the
for_each()
function to loop on the block pointer and index separately:namespace my { template<typename data_type, int block_size, typename FuncOfDataType> FuncOfDataType&& for_each(typename my::Container<data_type,block_size>::iterator i, typename my::Container<data_type,block_size>::iterator const&e, FuncOfDataType f) { for(; i.B != e.B; i.B++,i.I=0) for(; i.I != block_size; i.I++) f(*i); for(; i.I != e.I; i.I++) f(*i); return std::move(f); } } using my::for_each; // ensures that the appropriate using std::for_each; // version of for_each() is used
which requires only one comparison for most iterations and has no branches (note that branches can have a nasty impact on performance). Note that we don't need to define this in namespace
std
(which might be illegal), but can ensure that the correct version is used by appropriateusing
directives. This is equivalent tousing std::swap;
when specialisingswap()
for certain user-defined types.
a
for_each
可以接受任何适当的可调用对象,允许为不同的 for 循环“回收”循环体。例如(伪代码)for( range_1 ) { lengthy_loop_body } // many lines of code for( range_2 ) { lengthy_loop_body } // the same many lines of code again
变成
auto loop_body = some_lambda; // many lines of code here only std::for_each( range_1 , loop_body ); // a single line of code std::for_each( range_2 , loop_body ); // another single line of code
从而避免重复并简化代码维护。(当然,在有趣的混合风格中,也可以对
for
循环使用类似的方法。)另一个区别是跳出循环(在循环中
break
或return
在for
循环中)。据我所知,在for_each
循环中这只能通过抛出异常来完成。例如for( range ) { some code; if(condition_1) return x; // or break more code; if(condition_2) continue; yet more code; }
变成
try { std::for_each( range , [] (const_reference x) { some code; if(condition_1) throw x; more code; if(condition_2) return; yet more code; } ); } catch(const_reference r) { return r; }
对于具有循环体和函数体(循环周围)范围的对象调用析构函数具有相同的效果。
for_each
IMHO的主要好处是,当普通迭代效率不高时,可以为某些容器类型重载它。例如,考虑一个包含数据块链表的容器,每个块包含一个连续的元素数组,类似于(省略无关代码)namespace my { template<typename data_type, unsigned block_size> struct Container { struct block { const block*NEXT; data_type DATA[block_size]; block() : NEXT(0) {} } *HEAD; }; }
那么这种类型的适当前向迭代器将需要在每次增量时检查块的结尾,并且比较运算符需要比较块指针和每个块内的索引(省略不相关的代码):
namespace my { template<typename data_type, unsigned block_size> struct Container { struct iterator { const block*B; unsigned I; iterator() = default; iterator&operator=(iterator const&) = default; iterator(const block*b, unsigned i) : B(b), I(i) {} iterator& operator++() { if(++I==block_size) { B=B->NEXT; I=0; } // one comparison and branch return*this; } bool operator==(const iterator&i) const { return B==i.B && I==i.I; } // one or two comparisons bool operator!=(const iterator&i) const { return B!=i.B || I!=i.I; } // one or two comparisons const data_type& operator*() const { return B->DATA[I]; } }; iterator begin() const { return iterator(HEAD,0); } iterator end() const { return iterator(0,0); } }; }
例如,这种类型的迭代器与
for
and 一起for_each
工作my::Container<int,5> C; for(auto i=C.begin(); i!=C.end(); // one or two comparisons here ++i) // one comparison here and a branch f(*i);
但每次迭代需要两到三个比较以及一个分支。更有效的方法是重载
for_each()
函数以分别在块指针和索引上循环:namespace my { template<typename data_type, int block_size, typename FuncOfDataType> FuncOfDataType&& for_each(typename my::Container<data_type,block_size>::iterator i, typename my::Container<data_type,block_size>::iterator const&e, FuncOfDataType f) { for(; i.B != e.B; i.B++,i.I=0) for(; i.I != block_size; i.I++) f(*i); for(; i.I != e.I; i.I++) f(*i); return std::move(f); } } using my::for_each; // ensures that the appropriate using std::for_each; // version of for_each() is used
大多数迭代只需要进行一次比较,并且没有分支(请注意,分支会对性能产生严重影响)。请注意,我们不需要在命名空间中定义它
std
(这可能是非法的),但可以确保适当的using
指令使用正确的版本。这相当于using std::swap;
专门swap()
针对某些用户定义的类型。
回答by eq-
Regarding perfomance, your for
loop calls std::end
repeatedly, while std::for_each
will not. This might or might not result in a performance difference depending on the container used.
关于性能,您的for
循环会std::end
重复调用,而std::for_each
不会。这可能会或可能不会导致性能差异,具体取决于所使用的容器。
回答by Adrian McCarthy
The
std::for_each
version will visit each element exactly once. Somebody reading the code can know that as soon as they seestd::for_each
, as there's nothing that can be done in the lambda to mess with the iterator. In the traditional for loop, you have to study the body of the loop for unusual control flow (continue
,break
,return
) and dinking with the iterator (e.g., in this case, skip the next element with++it
).You can trivially change the algorithm in the lambda solution. For example, you could make an algorithm that visits every nth element. In many cases, you didn't really want a for loop anyway, but a different algorithm like
copy_if
. Using an algorithm+lambda, is often more amenable to change and is a bit more concise.On the flip side, programmers are much more used to traditional for loops, so they may find algorithm+lambda to be harder to read.
该
std::for_each
版本将只访问每个元素一次。阅读代码的人一看到就知道,因为std::for_each
在 lambda 中没有什么可以做来干扰迭代器。在传统的 for 循环中,您必须研究异常控制流 (continue
,break
,return
) 和迭代器的循环体(例如,在这种情况下,用 跳过下一个元素++it
)。您可以轻松地更改 lambda 解决方案中的算法。例如,您可以创建一个访问每个第 n 个元素的算法。在许多情况下,您实际上并不需要 for 循环,而是需要一种不同的算法,例如
copy_if
. 使用算法+lambda,通常更容易改变并且更简洁一些。另一方面,程序员更习惯于传统的 for 循环,因此他们可能会发现算法 + lambda 更难阅读。
回答by comonad
Indeed; in the case of using a Lambda expression, you have to declare the parameter type and name, so nothing is won.
的确; 在使用 Lambda 表达式的情况下,您必须声明参数类型和名称,因此什么也得不到。
But it will be awesome as soon as you want to call one (named) function or function-object with this. (Remember that you can combine function-like things via std::bind
.)
但是一旦你想用这个调用一个(命名的)函数或函数对象,它就会很棒。(请记住,您可以通过 组合类似函数的东西std::bind
。)
The books from Scott Meyers (I believe it was Effective STL) describe such programming styles very good and clear.
Scott Meyers 的书(我相信它是Effective STL)很好地和清晰地描述了这种编程风格。
回答by haohaolee
First, I cannot see much difference between these two, because for_each is implemented using for loop. But note that for_each is a function which has a return value.
首先,我看不出这两者之间有多大区别,因为 for_each 是使用 for 循环实现的。但请注意 for_each 是一个有返回值的函数。
Second, I will use range loop syntax once available in this case since this day would come soon anyway.
其次,我将在这种情况下使用一旦可用的范围循环语法,因为无论如何这一天很快就会到来。