与for循环相比,STD:for_each的优势

std::for_eachfor环有什么优势吗?对我来说,std::for_each似乎只是阻碍了代码的可读性。那么为什么一些编码标准推荐使用它呢?

136453 次浏览

您可以将迭代器作为对函数的调用,该函数在循环的每次迭代中执行。

请看这里: http://www.cplusplus.com/reference/algorithm/for_each/

for_each更通用。您可以使用它对任何类型的容器进行迭代(通过传入开始/结束迭代器)。您可以潜在地换出使用for_each的函数下的容器,而不必更新迭代代码。您需要考虑到除了std::vector和普通的老式C数组之外,世界上还有其他容器,才能看到for_each的优势。

for_each的主要缺点是它接受仿函数,因此语法很笨拙。这个问题在C++11(以前的C++0x)中通过引入lambdas得到了解决:

std::vector<int> container;
...
std::for_each(container.begin(), container.end(), [](int& i){
i+= 10;
});

三年后你就不会觉得奇怪了。

以下是一些原因:

  1. 它似乎阻碍了可读性,只是因为你不习惯它和/或没有使用正确的工具来使它变得非常简单。(有关帮助程序的信息,请参见Boost:Range和Boost:Bind/BoostLambda.其中许多将进入C++0x,并使_每个和相关的函数更有用。)

  2. 它允许您在之上为_编写一个算法,每个都可以与任何迭代器一起工作。

  3. 它减少了愚蠢的打字错误的机会。

  4. 它也打开了你的思维其余的STL算法,如find_ifsortreplace等,这些看起来不会那么奇怪了。这可能是一个巨大的胜利。

更新1:

最重要的是,它可以帮助您超越for_each与for循环,并查看其他STL-ALOG,如查找/排序/分区/复制_替换_if,并行执行..或者别的什么。

使用“ The Rest ”可以非常简洁地编写许多处理。但如果你所做的只是用各种内部逻辑编写一个for循环,那么你永远也学不会如何使用它们,你最终会一遍又一遍地发明轮子。

以及(即将上市的用于_的系列样式)+lambdas:

for_each(monsters, [](auto& m) { m.think(); });

IMO是否比以下内容更具可读性:

for (auto i = monsters.begin(); i != monsters.end(); ++i) {
i->think();
}

还有这个:

for_each(bananas, [&](auto& b) { my_monkey.eat(b); );

比以下内容更简洁:

for (auto i = bananas.begin(); i != bananas.end(); ++i) {
my_monkey->eat(*i);
}

但基于的新系列可能是最好的:

 for (auto& b : bananas)
my_monkey.eat(b);

但是for_each可能很有用,特别是当您有几个函数要按顺序调用,但需要在下一个..之前为所有对象运行每个方法时。但也许这只是我。;)

更新2:我已经编写了自己的STL算法的单行包装器,它处理范围而不是迭代器对。Boost:Range_EX一旦发布,就会包含它,也许它也会出现在C++0x中?

这是非常主观的,有些人会说使用for_each将使代码更多可读,因为它允许使用相同的约定来处理不同的集合。 for_each本身是作为一个循环

来实现的
template<class InputIterator, class Function>
Function for_each(InputIterator first, InputIterator last, Function f)
{
for ( ; first!=last; ++first ) f(*first);
return f;
}

所以这取决于你选择什么是正确的。

for用于循环,可以迭代每个元素或每三个元素等。for_each用于仅迭代每个元素。从它的名字就可以看出来。因此,更清楚您打算在代码中做什么。

与许多算法函数一样,最初的反应是认为使用Foreach比使用循环更不可读。这是许多激烈争论的话题。

一旦你习惯了这个习语,你会发现它很有用。一个明显的优点是,它迫使编码器将循环的内部内容与实际的迭代功能分离。(好的,我认为这是一个优势。其他人说你只是在切碎代码(没有真正的好处)。

另一个优点是,当我看到Foreach时,我__abc0,要么处理每一项,要么抛出一个异常。

为了环允许用于终止环的几个选项。您可以让循环运行其整个过程,也可以使用打破关键字显式跳出循环,或者使用返回关键字在循环中途退出整个函数。相反,福雷阿赫不允许这些选项,这使得它更具可读性。您只需浏览一下函数名,就可以了解迭代的全部性质。

下面是一个容易混淆的为了循环的示例:

for(std::vector<widget>::iterator i = v.begin(); i != v.end(); ++i)
{
/////////////////////////////////////////////////////////////////////
// Imagine a page of code here by programmers who don't refactor
///////////////////////////////////////////////////////////////////////
if(widget->Cost < calculatedAmountSofar)
{
break;
}
////////////////////////////////////////////////////////////////////////
// And then some more code added by a stressed out juniour developer
// *#&$*)#$&#(#)$#(*$&#(&*^$#(*$#)($*#(&$^#($*&#)$(#&*$&#*$#*)$(#*
/////////////////////////////////////////////////////////////////////////
for(std::vector<widgetPart>::iterator ip = widget.GetParts().begin(); ip != widget.GetParts().end(); ++ip)
{
if(ip->IsBroken())
{
return false;
}
}
}

for_each循环旨在对用户代码隐藏迭代器(循环如何实现的细节),并定义清晰的操作语义:每个元素只迭代一次。

当前标准中可读性的问题在于,它需要一个仿函数作为最后一个参数,而不是一个代码块,因此在许多情况下,您必须为它编写特定的仿函数类型。由于仿函数对象不能就地定义(在函数中定义的局部类不能用作模板参数),并且循环的实现必须从实际循环中移走,因此这会导致代码可读性降低。

struct myfunctor {
void operator()( int arg1 ) { code }
};
void apply( std::vector<int> const & v ) {
// code
std::for_each( v.begin(), v.end(), myfunctor() );
// more code
}

请注意,如果要对每个对象执行特定操作,则可以使用std::mem_fnboost::bind(下一个标准中的std::bind)或boost::lambda(下一个标准中的lambdas)来使其更简单:

void function( int value );
void apply( std::vector<X> const & v ) {
// code
std::for_each( v.begin(), v.end(), boost::bind( function, _1 ) );
// code
}

如果您确实有函数/方法可以调用,那么它的可读性并不比手卷版本差,而且更紧凑。该实现可以提供for_each循环的其他实现(考虑并行处理)。

即将推出的标准以不同的方式解决了一些缺点,它将允许本地定义的类作为模板的参数:

void apply( std::vector<int> const & v ) {
// code
struct myfunctor {
void operator()( int ) { code }
};
std::for_each( v.begin(), v.end(), myfunctor() );
// code
}

改进代码的局部性:当您浏览时,您可以看到它在那里做什么。事实上,您甚至不需要使用类语法来定义仿函数,而是在此处使用lambda:

void apply( std::vector<int> const & v ) {
// code
std::for_each( v.begin(), v.end(),
[]( int ) { // code } );
// code
}

即使对于for_each的情况,将存在使其更自然的特定构造:

void apply( std::vector<int> const & v ) {
// code
for ( int i : v ) {
// code
}
// code
}

我倾向于将for_each结构与手卷环混合。当我只需要调用现有的函数或方法时(for_each( v.begin(), v.end(), boost::bind( &Type::update, _1 ) )),我选择for_each构造,它从代码中去掉了大量的模板迭代器内容。当我需要一些更复杂的东西,并且我不能在实际使用的几行代码上实现一个仿函数时,我会滚动我自己的循环(保持操作到位)。在代码的非关键部分,我可能会使用Boost_Foreach(一位同事让我使用它)。

就我个人而言,任何时候我需要特意使用std::for_each(编写特殊用途的函子/复杂的boost::lambda),我发现BOOST_FOREACH和C++0x的基于范围的更清晰:

BOOST_FOREACH(Monster* m, monsters) {
if (m->has_plan())
m->act();
}

std::for_each(monsters.begin(), monsters.end(),
if_then(bind(&Monster::has_plan, _1),
bind(&Monster::act, _1)));

除了可读性和性能之外,一个经常被忽视的方面是一致性。有许多方法可以在迭代器上实现for(或while)循环,来自:

for (C::iterator iter = c.begin(); iter != c.end(); iter++) {
do_something(*iter);
}

向:

C::iterator iter = c.begin();
C::iterator end = c.end();
while (iter != end) {
do_something(*iter);
++iter;
}

在不同的效率水平和潜在的错误水平之间有许多例子。

但是,使用for_each可以通过抽象循环来增强一致性:

for_each(c.begin(), c.end(), do_something);

您现在唯一需要担心的是:您是使用Boost或C++0x特性将循环体实现为函数、仿函数还是lambda?就我个人而言,我宁愿担心这个问题,而不是如何实现或读取一个随机的for/while循环。

大多数情况下,你必须遍历整个集合。因此,我建议您编写自己的_each()变量,只接受2个参数。这将允许您将特里·马哈菲的例子重写为:

for_each(container, [](int& i) {
i += 10;
});

我认为这确实比for循环更具可读性。但是,这需要C++0x编译器扩展。

我曾经不喜欢std::for_each,并认为没有Lambda,它是完全错误的。然而,前一段时间我确实改变了主意,现在我真的很喜欢它。我认为它甚至提高了可读性,并使以TDD方式测试代码变得更容易。

std::for_each算法可以被读取为对范围内的所有元素执行某些操作,其提高了可读性。假设您要执行的操作有20行,而执行该操作的函数也大约有20行。这将使得具有常规FOR循环的函数长达40行,而具有std::for_each的函数仅长约20行,因此可能更容易理解。

std::for_each的函子更可能是更通用的,因此是可重用的,例如:

struct DeleteElement
{
template <typename T>
void operator()(const T *ptr)
{
delete ptr;
}
};

在代码中,你只需要像std::for_each(v.begin(), v.end(), DeleteElement())这样的一行程序,在我看来,这比显式循环要稍微好一点。

在单元测试中,所有这些函子通常都比长函数中间的显式for循环更容易获得,仅这一点对我来说就已经是一个巨大的胜利。

std::for_each通常也更可靠,因为您不太可能在范围上出错。

最后,编译器可能会为std::for_each生成比某些类型的手工for循环稍好的代码,因为它(对于_每个)总是看起来与编译器相同,并且编译器编写者可以使用他们所有的知识来使其尽可能好。

这同样适用于其他STD算法,如find_iftransform等。

C++11(以前称为C++0x)的好处是,这个令人厌烦的争论将得到解决。

我的意思是,没有一个头脑正常的人,想要在整个集合上迭代的人,仍然会使用它。

for(auto it = collection.begin(); it != collection.end() ; ++it)
{
foo(*it);
}

或者这个

for_each(collection.begin(), collection.end(), [](Element& e)
{
foo(e);
});

基于范围的for循环语法可用时:

for(Element& e : collection)
{
foo(e);
}

这种语法在Java和C#中已经存在一段时间了,实际上,在我最近看到的Java或C#代码中,foreach循环比经典的for循环要多得多。

我发现对于_来说,每一个都不利于可读性。这是一个很好的概念,但C++使它很难写成可读的,至少对我来说是这样。C++0x Lamda表达式将有所帮助。我真的很喜欢拉姆达的想法。然而,乍一看,我认为语法是非常丑陋的,我不能100%确定我会习惯它。也许5年后我会习惯它,不会再去想它,但也许不会。时间会证明一切:)

我更喜欢用

vector<thing>::iterator istart = container.begin();
vector<thing>::iterator iend = container.end();
for(vector<thing>::iterator i = istart; i != iend; ++i) {
// Do stuff
}

我发现显式的for循环读起来更清晰,而且显式地使用命名变量作为开始和结束迭代器减少了for循环中的混乱。

当然,情况各不相同,这正是我通常认为最好的。

你基本上是正确的:大多数时候,std::for_each是净损失。我甚至可以比较for_eachgotogoto提供了最通用的流控制——你可以用它来实现你所能想象到的任何其他控制结构。然而,这种多功能性意味着,孤立地看到goto,实际上就会告诉你它在这种情况下打算做什么。因此,除了作为最后的手段,几乎没有人会在头脑正常的情况下使用goto

在这些标准算法中,for_each与之非常相似--它实际上可以用来实现任何东西,这意味着看到for_each实际上并不能告诉您它在这种情况下的用途。不幸的是,人们对for_each的态度就像他们对goto的态度一样,(比如说)在1970年左右--很少的,人们已经意识到它只能作为最后的手段,但许多人仍然认为它是主要的算法,很少使用任何其他算法。绝大多数时候,即使只是匆匆一瞥,也会发现其中一种选择非常优越。

举个例子,我很确定我已经忘记了有多少次看到人们使用for_each编写代码来打印出集合的内容。根据我看到的帖子,这可能是for_each最常见的用法。他们最终会得到这样的结果:

class XXX {
// ...
public:
std::ostream &print(std::ostream &os) { return os << "my data\n"; }
};

他们的帖子询问bind1stmem_fun等的组合。他们需要做这样的事情:

std::vector<XXX> coll;


std::for_each(coll.begin(), coll.end(), XXX::print);

工作,并打印出coll的元素。如果它真的像我写的那样工作,它将是平庸的,但它不是--当你让它工作的时候,很难在将它结合在一起的片段中找到与正在发生的事情相关的那几位代码。

幸运的是,有一个更好的方法。为XXX添加一个正常的流插入器重载:

std::ostream &operator<<(std::ostream *os, XXX const &x) {
return x.print(os);
}

并使用std::copy

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

这是可行的--并且实际上不需要做任何工作就可以知道它将coll的内容打印到std::cout

当__(ABC0)和__(ABC1)时,写函数更具可读性的优点可能不会显现出来。

如果使用functional.H中的所有算法,而不是使用for循环,代码的可读性会大大提高。

iterator longest_tree = std::max_element(forest.begin(), forest.end(), ...);
iterator first_leaf_tree = std::find_if(forest.begin(), forest.end(), ...);
std::transform(forest.begin(), forest.end(), firewood.begin(), ...);
std::for_each(forest.begin(), forest.end(), make_plywood);

许多的可读性是否高于;

Forest::iterator longest_tree = it.begin();
for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{
if (*it > *longest_tree) {
longest_tree = it;
}
}


Forest::iterator leaf_tree = it.begin();
for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{
if (it->type() == LEAF_TREE) {
leaf_tree  = it;
break;
}
}


for (Forest::const_iterator it = forest.begin(), jt = firewood.begin();
it != forest.end();
it++, jt++) {
*jt = boost::transformtowood(*it);
}


for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{
std::makeplywood(*it);
}

这就是我认为很好的地方,将for循环推广到一行函数=)

For循环可以中断; 我不想成为赫伯·萨特的鹦鹉,所以这里是他演讲的链接: http://channel9.msdn.com/events/build/build2011/tool-835t. 请务必阅读评论:)

如果经常使用STL中的其他算法,for_each有几个优点:

  1. 它通常比for循环更简单,更不容易出错,部分原因是您将习惯于使用此接口的函数,部分原因是在许多情况下它实际上更简洁一些。
  2. 尽管基于范围的for循环可以更简单,但它不够灵活(正如Adrian McCarthy所指出的,它在整个容器上迭代)。
  3. 传统的for循环不同,__abc0强制您编写适用于任何输入迭代器的代码。以这种方式受到限制实际上是一件好事,因为:

    1. 实际上,您可能需要调整代码,以便稍后为不同的容器工作。
    2. 在开始的时候,它可能会教你一些东西和/或让你的习惯变得更好。
    3. 即使您总是编写完全等价的FOR循环,其他修改相同代码的人可能也不会这样做,因为系统会提示您使用for_each
  4. 使用for_each有时可以更明显地表明,您可以使用更具体的STL函数来执行相同的操作。(如杰里·科芬的例子;__abc0不一定是最好的选择,但for循环也不是唯一的选择。)

使用C++11和两个简单的模板,您可以编写

        for ( auto x: range(v1+4,v1+6) ) {
x*=2;
cout<< x <<' ';
}

作为for_each或环路的替代。为什么选择它可以归结为简洁和安全,在不存在的表达中没有错误的机会。

对我来说,当循环体已经是一个函子时,for_each总是更好,我会利用我能得到的任何优势。

你仍然使用三个表达式for,但现在当你看到一个你知道有一些东西需要理解,它不是样板文件。我憎恶样板文件。我憎恨它的存在。它不是真正的代码,没有什么可以通过阅读来学习的,它只是另一件需要检查的事情。精神上的努力可以通过检查它是否容易生疏来衡量。

模板是

template<typename iter>
struct range_ {
iter begin() {return __beg;}    iter end(){return __end;}
range_(iter const&beg,iter const&end) : __beg(beg),__end(end) {}
iter __beg, __end;
};


template<typename iter>
range_<iter> range(iter const &begin, iter const &end)
{ return range_<iter>(begin,end); }

简单:当你已经有一个函数来处理每个数组项时,__abc0是有用的,所以你不必写一个lambda.当然,这个

for_each(a.begin(), a.end(), a_item_handler);

for(auto& item: a) {
a_item_handler(a);
}

此外,范围for循环仅从开始到结束在整个容器上迭代,而for_each更灵活。

for_each允许我们实现分叉连接模式。除此之外,它支持流畅界面

分叉连接模式

我们可以添加实现gpu::for_each,通过在多个工作者中调用Lambda任务来使用CUDA/GPU进行异构并行计算。

gpu::for_each(users.begin(),users.end(),update_summary);
// all summary is complete now
// go access the user-summary here.

并且gpu::for_each可以在执行下一语句之前等待工作者对所有λ任务的工作完成。

流畅界面

它允许我们以简洁的方式编写人类可读的代码。

accounts::erase(std::remove_if(accounts.begin(),accounts.end(),used_this_year));
std::for_each(accounts.begin(),accounts.end(),mark_dormant);
其他答案中

有很多很好的理由,但似乎都忘记了这一点。 当for循环总是以begin()迭代器开始时,for_each允许您使用Reverse或几乎任何自定义迭代器。

反向迭代器示例:

std::list<int> l {1,2,3};
std::for_each(l.rbegin(), l.rend(), [](auto o){std::cout<<o;});

使用一些自定义树迭代器的示例:

SomeCustomTree<int> a{1,2,3,4,5,6,7};
auto node = a.find(4);
std::for_each(node.breadthFirstBegin(), node.breadthFirstEnd(), [](auto o){std::cout<<o;});

当你不要有一个范围时,std::for_each是很好的。

例如,考虑std::istream_iterator

using Iter = std::istream_iterator<int>;


for (Iter i(str); i != Iter(); ++i) {
f(*i);
}

它没有容器,因此您不能轻松地使用for (auto &&item: ...)循环,但您可以:

std::for_each(Iter(str), Iter(), [](int item)
// ...
});