for loop vs std :: for_each with lambda

And*_*rey 36 c++ lambda c++11

让我们考虑一个用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
    });
  }
Run Code Online (Sandbox Code Playgroud)

什么是for loop vs的优缺点std::for_each:

一场表演?(我不希望有任何区别)

b)可读性和可维护性?

在这里,我看到了许多缺点for_each.循环会不会接受c风格的数组.lambda形式参数的声明是如此冗长,不可能在auto那里使用.不可能突破for_each.

在C++之前的11天里,反对for是需要为迭代器指定类型(不再容纳)以及很容易错误地输入循环条件(我从未在10年内做过这样的错误).

作为结论,我for_each的观点与普遍观点相矛盾.我在这里错过了什么?

Wal*_*ter 32

我认为到目前为止,答案尚未涵盖其他一些差异.

  1. 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
    
    Run Code Online (Sandbox Code Playgroud)

    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
    
    Run Code Online (Sandbox Code Playgroud)

    从而避免重复并简化代码维护.(当然,在一个有趣的混合风格中,人们也可以使用与for循环类似的方法.)

  2. 另一个不同之处关于终止循环的(有breakreturnfor循环).据我所知,在一个for_each循环中,这只能通过抛出异常来完成.例如

    for( range )
    {
      some code;
      if(condition_1) return x; // or break
      more code;
      if(condition_2) continue;
      yet more code;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    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; }
    
    Run Code Online (Sandbox Code Playgroud)

    关于调用具有循环体和函数体(围绕循环)范围的对象的析构函数具有相同的效果.

  3. for_each恕我直言的主要好处是,当普通迭代效率不高时,可以为某些容器类型重载它.例如,考虑一个容器,它包含一个数据块的链接列表,每个块包含一个连续的元素数组,类似于(省略不相关的代码)

    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;
      };
    }
    
    Run Code Online (Sandbox Code Playgroud)

    然后对于这种类型的适当的前向迭代将需要检查的块在每个增量处的端部和比较运算需要比较两个块指针和每一个块(省略不相关的代码)中的索引:

    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); }
      };
    }
    
    Run Code Online (Sandbox Code Playgroud)

    这种类型的迭代器的正常工作与forfor_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);
    
    Run Code Online (Sandbox Code Playgroud)

    但每次迭代需要两到三次比较以及分支.一种更有效的方法是重载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
    
    Run Code Online (Sandbox Code Playgroud)

    这对于大多数迭代只需要一次比较而且没有分支(注意分支可能对性能产生严重影响).请注意,我们不需要在命名空间中定义它std(这可能是非法的),但可以确保适当的using指令使用正确的版本.这相当于using std::swap;专门swap()针对某些用户定义的类型.


eq-*_*eq- 7

关于性能,你的for循环std::end重复调用,而std::for_each不是.这可能会也可能不会导致性能差异,具体取决于所使用的容器.