代码之家  ›  专栏  ›  技术社区  ›  missingfaktor Kevin Wright

std的优点:对于每个循环

  •  147
  • missingfaktor Kevin Wright  · 技术社区  · 15 年前

    有什么好处吗 std::for_each 结束 for 循环?对我来说, 性病:每一个 似乎只会妨碍代码的可读性。为什么有些编码标准建议使用它?

    20 回复  |  直到 6 年前
        1
  •  161
  •   Marc.2377    8 年前

    和…的好事情 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语言中已经有一段时间了,实际上还有更多的方法。 foreach 循环比经典 对于 我最近看到的每一个Java或C代码中的循环。

        2
  •  46
  •   steffen    6 年前

    以下是一些原因:

    1. 它似乎阻碍了可读性,仅仅是因为您不习惯它和/或不使用正确的工具来使它变得非常容易。(有关帮助程序,请参见Boost::Range和Boost::Bind/Boost::Lambda。其中许多将进入C++0X,并使FoLY和相关函数更加有用。

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

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

    4. 它也会让你的思想向其他STL算法敞开心扉,比如 find_if , sort , replace 等等,这些看起来不再那么奇怪了。这可能是一场巨大的胜利。

    更新1:

    最重要的是,它能帮助你超越 for_each 与之相比,对于这样的循环,还有其他的stl alogs,比如find/sort/partition/copy,replace-if,parallel execution。或者什么。

    很多处理过程可以用for-each的兄弟姐妹的“其余部分”非常简洁地编写,但是如果您所做的只是用各种内部逻辑编写for循环,那么您将永远不会学习如何使用这些循环,最终您将一次又一次地发明轮子。

    和(即将推出的每个系列的款式):

    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算法包装器,它使用范围而不是一对迭代器。BooG::RangeEX EX,一旦发布,将包括它,也许它也会出现在C++0X中吗?

        3
  •  22
  •   Terry Mahaffey    15 年前

    因为每个都更通用。您可以使用它在任何类型的容器上迭代(通过传入begin/end迭代器)。您可以在每个函数下面交换容器,而不必更新迭代代码。您需要考虑世界上除了std::vector和普通的旧C数组之外还有其他容器,以了解它们各自的优势。

    for-each的主要缺点是它需要一个函数,所以语法比较繁琐。这是固定在C++0x中引入LAMBDAS:

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

    这在3年内不会让你觉得奇怪。

        4
  •  17
  •   Macke    7 年前

    就我个人而言,任何时候我都需要用我的方式 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)));
    
        5
  •  11
  •   Alon    15 年前

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

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

    所以,选择适合你的东西取决于你。

        6
  •  10
  •   Andrew Shepherd    15 年前

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

    一旦你习惯了这个成语,你会发现它很有用。一个明显的优点是它强制编码人员将循环的内部内容与实际的迭代功能分开。(好吧,我认为这是一个优势。另一种说法是,你只是在断章取义,没有真正的好处)。

    另一个好处是当我看到foreach时, 知道 将处理每个项或引发异常。

    对于 循环允许终止循环的多个选项。您可以让循环运行其整个过程,也可以使用 打破 关键字以显式跳出循环,或使用 返回 关键字退出整个函数的中间循环。相反, 前额 不允许使用这些选项,这使其更具可读性。您只需浏览函数名,就可以了解迭代的完整性质。

    下面是一个令人困惑的例子 对于 循环:

    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;
          }
       }
    }
    
        7
  •  9
  •   Jerry Coffin    15 年前

    你基本上是对的:大多数时候, std::for_each 是净损失。我会去比较一下 for_each goto . 古托 提供了尽可能多的流控制——您可以使用它来实现您所能想象的任何其他控制结构。然而,这种多功能性意味着 古托 孤立地告诉你 没有什么 关于在这种情况下它打算做什么。结果,几乎没有人在他们的头脑中使用 古托 除了作为最后的手段。

    在标准算法中, 每一个 基本上是相同的方式——它可以用来实现几乎任何事情,这意味着看到 为每个人 几乎不告诉你它在这种情况下的用途。不幸的是,人们对 每一个 是关于他们的态度 古托 大约在1970年 很少的 人们已经意识到它只能作为最后的手段使用,但许多人仍然认为它是主要的算法,而且很少使用其他算法。绝大多数情况下,即使是快速的一瞥,也会发现其中一种选择是非常优越的。

    举个例子,我很肯定我已经不知道有多少次我看到人们用 为每个人 . 根据我看到的帖子,这很可能是 每一个 . 最后会出现如下情况:

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

    工作并打印出 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"));
    

    这确实有效——而且几乎不需要任何工作就可以发现它打印了 科尔 std::cout .

        8
  •  8
  •   Viktor Sehr    15 年前

    为了提高可读性而编写函数的优势可能不会在 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循环归纳为一行函数=)

        9
  •  5
  •   Tigran Saluev    7 年前

    容易的: for_each 当您已经有了一个函数来处理每个数组项时非常有用,因此不必编写lambda。当然,这个

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

    比…好

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

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

        10
  •  4
  •   David Rodríguez - dribeas    15 年前

    这个 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_fn ,或 boost::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
    }
    

    如果您确实有函数/方法要调用,那么它的可读性和紧凑性都不比手动版本差。该实现可以提供 每一个 循环(考虑并行处理)。

    即将到来的标准以不同的方式处理了一些缺点,它将允许本地定义的类作为模板的参数:

    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
    }
    

    即使对于 每一个 将有一个特定的结构,使其更自然:

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

    我倾向于混合 每一个 用手摇环构造。当我只需要调用现有的函数或方法时( for_each( v.begin(), v.end(), boost::bind( &Type::update, _1 ) ) 我去追求 每一个 构造,从代码中去掉了很多锅炉板迭代器的东西。当我需要更复杂的东西,并且我不能在实际使用之上仅仅实现几行函数时,我会滚动自己的循环(保持操作到位)。在代码的非关键部分,我可能会使用boost-foreach(一个同事把我引入其中)

        11
  •  3
  •   Jason Govig    15 年前

    除了可读性和性能,一个通常被忽视的方面是一致性。有许多方法可以实现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循环。

        12
  •  3
  •   Dmitry    15 年前

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

    这个 性病:每一个 算法可以读取为 对范围内的所有元素执行某些操作 哪一个 可以 提高可读性。假设您要执行的操作有20行长,执行该操作的函数也有20行长。这将使函数40行长,使用传统的for循环,而使用 性病:每一个 因此可能更容易理解。

    函子 性病:每一个 更可能是通用的,因此可重复使用,例如:

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

    在代码中,你只有一个像 std::for_each(v.begin(), v.end(), DeleteElement()) 这比显式循环稍微好一点。

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

    性病:每一个 通常也更可靠,因为你不太可能在射程上犯错误。

    最后,编译器可能会为 性病:每一个 而不是某些手工制作的循环,因为它(为每个) 总是 对于编译器来说是一样的,编译器编写者可以把他们所有的知识都放在一起,尽可能地完善它。

    同样适用于其他标准算法,如 find_if , transform 等。

        13
  •  2
  •   Kirill V. Lyadvinsky    15 年前

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

        14
  •  2
  •   Sean Patrick Santos    11 年前

    如果您经常使用STL中的其他算法,那么 for_each :

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

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

        15
  •  2
  •   jthill    11 年前

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

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

    代替 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); }
    
        16
  •  1
  •   Community CDub    7 年前

    大多数情况下,你必须 迭代整个集合 . 因此,我建议您编写自己的for each()变量,只使用2个参数。这将允许您重写 Terry Mahaffey's example 作为:

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

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

        17
  •  1
  •   jcoder    15 年前

    我发现每个人的可读性都不好。这个概念是好的,但是C++使它很难写可读,至少对我来说是如此。C++ 0xLAMDA表达式将有所帮助。我真的很喜欢拉姆达斯这个主意。不过,乍一看,我觉得语法很难看,我不完全肯定我会习惯它。也许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循环中的混乱。

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

        18
  •  0
  •   sdornan    15 年前

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

    请参见此处: http://www.cplusplus.com/reference/algorithm/for_each/

        19
  •  0
  •   NoSenseEtAl    13 年前

    因为循环可能中断; 我不想成为Herb Sutter的鹦鹉,下面是他的演讲链接: http://channel9.msdn.com/Events/BUILD/BUILD2011/TOOL-835T 请务必同时阅读评论:)

        20
  •  0
  •   KRoy    6 年前

    for_each 允许我们实施 Fork-Join pattern . 除了它所支持的 fluent-interface .

    分叉连接模式

    我们可以添加实现 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::福尔每 在执行下一个语句之前,可能等待工作人员完成所有lambda任务。

    Fluent接口

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

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