This question already has an answer here:
这个问题已经有了答案:
- Advantages of std::for_each over for loop 19 answers
- std的优点:for_each / for loop 19答案
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).
在pre- c++ 11天中,反对for的理由是需要为迭代器指定类型(不再保存),而且很容易出现错误的循环条件(我10年来从未犯过这样的错误)。
As a conclusion, my thoughts about for_each
contradict the common opinion. What am I missing here?
综上所述,我对for_each的看法与大家的看法是矛盾的。我错过了什么?
5 个解决方案
#1
31
I think there are some other differences not yet covered by the answers so far.
我认为,到目前为止,这些问题的答案还没有涵盖其他一些不同之处。
-
a
for_each
can accept any appropriate callable object, allowing one to 'recycle' the loop body for different for loops. For example (pseudo code)for_each可以接受任何适当的可调用对象,允许一个人“回收”循环体,使其在循环中有所不同。例如(伪代码)
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 example另一个区别是跳出循环(在for循环中有中断或返回)。就我所知,在for_each循环中,这只能通过抛出一个异常来完成。例如
for( 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)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; }; }
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 example例如,这种类型的迭代器可以正确地使用for和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);
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:但是每次迭代需要2到3个比较以及一个分支。更有效的方法是重载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
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.对于大多数迭代,这只需要一个比较,并且没有分支(注意,分支可能会对性能产生严重的影响)。注意,我们不需要在命名空间std中定义这个(这可能是非法的),但是可以确保通过适当的使用指令使用正确的版本。这相当于使用std:::swap;当为特定的用户定义类型指定swap()时。
#2
7
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不会。这可能导致性能差异,也可能不会,这取决于使用的容器。
#3
4
-
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
).for_each版本将访问每个元素一次。阅读代码的人可以在他们看到std::for_each的时候就知道,因为在lambda中没有什么可以处理迭代器。在传统的for循环中,您必须研究异常控制流的循环体(continue, break, return),并使用迭代器(例如,在本例中,使用+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.你可以简单地改变解中的算法。例如,您可以创建一个访问第n个元素的算法。在很多情况下,你并不是真的想要一个for循环,而是一个不同的算法,比如copy_if。使用算法+lambda通常更容易修改,也更简洁。
-
On the flip side, programmers are much more used to traditional for loops, so they may find algorithm+lambda to be harder to read.
另一方面,程序员更习惯于传统的for循环,所以他们可能会发现算法+lambda更难读。
#4
0
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.
其次,我将在本例中使用范围循环语法,因为这一天很快就会到来。
#5
0
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)的书(我相信它是有效的STL)描述了这种编程风格,非常好和清晰。
#1
31
I think there are some other differences not yet covered by the answers so far.
我认为,到目前为止,这些问题的答案还没有涵盖其他一些不同之处。
-
a
for_each
can accept any appropriate callable object, allowing one to 'recycle' the loop body for different for loops. For example (pseudo code)for_each可以接受任何适当的可调用对象,允许一个人“回收”循环体,使其在循环中有所不同。例如(伪代码)
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 example另一个区别是跳出循环(在for循环中有中断或返回)。就我所知,在for_each循环中,这只能通过抛出一个异常来完成。例如
for( 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)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; }; }
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 example例如,这种类型的迭代器可以正确地使用for和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);
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:但是每次迭代需要2到3个比较以及一个分支。更有效的方法是重载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
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.对于大多数迭代,这只需要一个比较,并且没有分支(注意,分支可能会对性能产生严重的影响)。注意,我们不需要在命名空间std中定义这个(这可能是非法的),但是可以确保通过适当的使用指令使用正确的版本。这相当于使用std:::swap;当为特定的用户定义类型指定swap()时。
#2
7
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不会。这可能导致性能差异,也可能不会,这取决于使用的容器。
#3
4
-
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
).for_each版本将访问每个元素一次。阅读代码的人可以在他们看到std::for_each的时候就知道,因为在lambda中没有什么可以处理迭代器。在传统的for循环中,您必须研究异常控制流的循环体(continue, break, return),并使用迭代器(例如,在本例中,使用+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.你可以简单地改变解中的算法。例如,您可以创建一个访问第n个元素的算法。在很多情况下,你并不是真的想要一个for循环,而是一个不同的算法,比如copy_if。使用算法+lambda通常更容易修改,也更简洁。
-
On the flip side, programmers are much more used to traditional for loops, so they may find algorithm+lambda to be harder to read.
另一方面,程序员更习惯于传统的for循环,所以他们可能会发现算法+lambda更难读。
#4
0
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.
其次,我将在本例中使用范围循环语法,因为这一天很快就会到来。
#5
0
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)的书(我相信它是有效的STL)描述了这种编程风格,非常好和清晰。