std :: for_each优于for循环的优点

时间:2010-01-12 07:37:05

标签: c++ stl foreach coding-style

std::for_each优于for循环有什么优势吗?对我而言,std::for_each似乎只会阻碍代码的可读性。为什么有些编码标准推荐使用?

21 个答案:

答案 0 :(得分:163)

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循环。< / p>

答案 1 :(得分:46)

以下是一些原因:

  1. 它似乎只是因为你不习惯它和/或没有使用正确的工具来使它变得非常简单而妨碍了可读性。 (请参阅boost :: range和boost :: bind / boost :: lambda for helpers。其中许多将进入C ++ 0x并使for_each和相关函数更有用。)

  2. 它允许您在for_each之上编写一个算法,该算法适用于任何迭代器。

  3. 它减少了愚蠢输入错误的机会。

  4. 它还会让您开始思考STL算法的其余部分,例如find_ifsortreplace等,这些看起来不再那么奇怪了。这可能是一个巨大的胜利。

  5. 更新1:

    最重要的是,它可以帮助你超越for_each而不是那样的for-loops,并查看其他STL-alogs,例如find / sort / partition / copy_replace_if,parallell execution ..或者不管。

    使用for_each的兄弟姐妹的“其余”可以非常简洁地编写很多处理,但如果你所做的只是编写一个带有各种内部逻辑的for循环,那么你将永远不会学习如何使用它们,你最终会一遍又一遍地发明轮子。

    (即将推出的范围式for_each):

    for_each(monsters, boost::mem_fn(&Monster::think));
    

    或使用C ++ x11 lambdas:

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

    比IMO更具可读性

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

    此外(或与lambdas,见其他人):

    for_each(bananas, boost::bind(&Monkey::eat, my_monkey, _1));
    

    比以下更简洁:

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

    特别是如果你有几个功能要按顺序调用......但也许这只是我。 ;)

    更新2 :我编写了自己的stl-algos单行封装器,它使用范围而不是迭代器对。 boost :: range_ex一旦发布,将包含它,也许它也会在C ++ 0x中存在?

答案 2 :(得分:22)

for_each更通用。您可以使用它来迭代任何类型的容器(通过传入开始/结束迭代器)。您可以在使用for_each的函数下面交换容器,而无需更新迭代代码。您需要考虑世界上还有其他容器而不是std::vector和普通的旧C数组,以便了解for_each的优势。

for_each的主要缺点是它需要一个仿函数,因此语法很笨拙。这是在C ++ 0x中修复的,引入了lambdas:

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

这对你来说在3年内看起来并不奇怪。

答案 3 :(得分:17)

就个人而言,任何时候我都需要不遗余力地使用std::for_each(编写专用函子/复杂boost::lambda),我发现BOOST_FOREACH和C + + 0x基于范围,更清晰:

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

VS

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

答案 4 :(得分:11)

它非常主观,有人会说使用for_each会使代码更多可读,因为它允许使用相同的约定处理不同的集合。 for_each itslef实现为循环

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

因此,您可以自行选择适合您的产品。

答案 5 :(得分:10)

与许多算法函数一样,最初的反应是认为使用foreach比使用循环更难以理解。这是许多火焰战争的主题。

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

另一个优点是,当我看到foreach时,我知道要么处理每个项目,要么抛出异常。

循环允许多个选项来终止循环。您可以让循环运行完整的过程,或者您可以使用 break 关键字显式跳出循环,或使用 return 关键字退出整个函数mid -环。相比之下, foreach 不允许这些选项,这使它更具可读性。您只需浏览一下函数名称,就可以了解迭代的完整性。

这是一个令人困惑的 for 循环的例子:

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;
      }
   }
}

答案 6 :(得分:10)

你大多是正确的:大部分时间,std::for_each都是净损失。我甚至将for_eachgoto进行比较。 goto提供了最通用的流量控制 - 您可以使用它来实现您可以想象的几乎任何其他控制结构。然而,这种多功能性意味着单独看到goto会告诉您几乎没有没有关于在这种情况下它打算做什么。因此,除了作为最后的手段之外,几乎没有人会使用goto

在标准算法中,for_each的方式大致相同 - 它可以用来实现几乎任何东西,这意味着看到for_each几乎没有告诉你它在这个方面的用途。情况。不幸的是,人们对for_each的态度是关于他们对{​​{1}}的态度在(比方说)1970年左右 - 一个少数的人已经抓住了这个事实应该是仅作为最后的手段使用,但许多人仍将其视为主要算法,并且很少使用任何其他算法。绝大多数时候,即使是快速浏览,也会发现其中一种替代方案非常优越。

例如,我很确定我已经忘记了多少次我看到人们编写代码以使用goto打印出集合的内容。根据我看过的帖子,这可能是for_each最常见的用法。他们最终得到了类似的东西:

for_each

他们的帖子询问他们需要做出类似的class XXX { // ... public: std::ostream &print(std::ostream &os) { return os << "my data\n"; } }; bind1st等组合:

mem_fun

工作,打印出std::vector<XXX> coll; std::for_each(coll.begin(), coll.end(), XXX::print); 的元素。如果它确实像我在那里写的那样工作,那将是平庸的,但它不会 - 当你使它工作时,很难找到与那些相关的那些代码在将它们组合在一起的各个部分中进行。

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

coll

并使用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

答案 7 :(得分:8)

for(...)for_each(...)时,可能不会显示写作功能更具可读性的优势。

如果你使用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循环推广到一行函数=)

答案 8 :(得分:5)

简单:for_each在您已经拥有处理每个数组项的函数时很有用,因此您不必编写lambda。当然,这个

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

优于

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

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

答案 9 :(得分:4)

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构造,它会从代码中删除大量的boater板迭代器。当我需要更复杂的东西而且我不能在实际使用之上几行实现仿函数时,我会自己循环(保持操作到位)。在代码的非关键部分,我可能会选择BOOST_FOREACH(一位同事让我进入它)

答案 10 :(得分:3)

我曾经不喜欢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())一样的单行,这比IMO略显优于IMO。

所有这些仿函数在单元测试中通常比在长函数中间的显式for循环更容易,而这对我来说已经是一个很大的胜利。

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

最后,编译器可能会为std::for_each生成比某些类型的手工制作for循环更好的代码,因为它(for_each)始终对于编译器和编译器看起来是相同的作家可以把他们所有的知识放在尽可能好的地方。

同样适用于find_iftransform等其他标准算法。

答案 11 :(得分:3)

除了可读性和性能之外,通常忽略的一个方面是一致性。有许多方法可以在迭代器上实现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?就个人而言,我更担心的是,如何实现或读取随机的/ while循环。

答案 12 :(得分:2)

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

  1. 它通常比for循环更简单且更不容易出错,部分原因是因为你习惯了这个界面的功能,部分原因是它在很多情况下实际上更简洁。
  2. 虽然基于范围的for循环可以更简单,但它的灵活性较低(正如Adrian McCarthy所指出的,它遍及整个容器)。
  3. 与传统的for循环不同,for_each强制您编写适用于任何输入迭代器的代码。以这种方式受到限制实际上是一件好事,因为:

    1. 您可能实际上需要调整代码以便以后适用于其他容器。
    2. 一开始,它可能会教你一些事情和/或改善你的习惯。
    3. 即使您总是编写完全等效的循环,其他修改相同代码的人也可能不会在不提示使用for_each的情况下执行此操作。
  4. 使用for_each有时会使您更明显地使用更具体的STL函数来执行相同的操作。 (正如Jerry Coffin的例子; for_each不是最好的选择,但for循环并不是唯一的选择。)

答案 13 :(得分:2)

使用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); }

答案 14 :(得分:2)

for是for循环,可以迭代每个元素或每三分之一等。for_each仅用于迭代每个元素。它的名字很清楚。因此,您更清楚自己打算在代码中做些什么。

答案 15 :(得分:1)

我觉得for_each对可读性不好。这个概念很好,但是c ++使得编写可读性非常困难,至少对我而言。 c ++ 0x lamda表达式会有所帮助。我非常喜欢lamdas的想法。但是乍看之下我认为语法非常难看,我不是百分之百确定我会习惯它。也许在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循环中的混乱。

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

答案 16 :(得分:1)

大多数情况下,您必须遍历整个集合。因此,我建议您编写自己的for_each()变体,只需要2个参数。这样您就可以将Terry Mahaffey's example重写为:

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

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

答案 17 :(得分:0)

你可以让迭代器调用一个函数,该函数在循环的每次迭代中执行。

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

答案 18 :(得分:0)

For循环可以打破; 我不想成为Herb Sutter的鹦鹉,所以这里是他演讲的链接: http://channel9.msdn.com/Events/BUILD/BUILD2011/TOOL-835T 一定要阅读评论:)

答案 19 :(得分:0)

for_each允许我们实现Fork-Join pattern。除此之外,它还支持fluent-interface

叉形连接模式

我们可以添加实现gpu::for_each,以通过在多个worker中调用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可能要等到工人完成所有lambda任务后,再执行下一条语句。

流利界面

它使我们能够以简洁的方式编写人类可读的代码。

accounts::erase(std::remove_if(accounts.begin(),accounts.end(),used_this_year));
std::for_each(accounts.begin(),accounts.end(),mark_dormant);

答案 20 :(得分:0)

其他答案中有很多充分的理由,但所有人似乎都忘记了 当for循环始终以for_each迭代器开始时,begin()允许您使用反向或几乎任何自定义迭代器。

带有反向迭代器的示例:

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;});