对于循环与标准::for_each与 lambda
for loop vs std::for_each with lambda
让我们考虑一个用 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
});
}
在以下方面,循环与std::for_each
的优缺点是什么:
a) 性能?(我不希望有任何区别)
b) 可读性和可维护性?
在这里,我看到了for_each
的许多缺点.它不会接受 c 样式数组,而循环会。lambda 形式参数的声明非常冗长,无法在那里使用auto
。不可能突破for_each
.
在 C++11 天之前,反对for
的论点是需要指定迭代器的类型(不再成立)以及很容易错误键入循环条件的可能性(我从来没有犯过这样的错误10 年)。
总而言之,我对for_each
的想法与普遍观点相矛盾。我在这里错过了什么?
到目前为止,答案中还没有涵盖其他一些差异。
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
成为
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
从而避免重复并简化代码维护。(当然,在一个有趣的风格组合中,人们也可以对
for
循环使用类似的方法。另一个区别是打破循环(
for
循环中有break
或return
)。据我所知,在for_each
循环中,这只能通过抛出异常来完成。例如for( range ) { some code; if(condition_1) return x; // or break more code; if(condition_2) continue; yet more code; }
成为
对于具有循环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; }
主体和函数体(围绕循环)范围的对象的析构函数的调用具有相同的效果。
恕我直言,
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; }; }
然后,这种类型的适当前向迭代器需要在每个增量处检查块的末尾,并且比较运算符需要比较块指针和每个块中的索引(省略不相关的代码):
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); } }; }
这种类型的迭代器可以正常工作
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);
但每次迭代需要两到三次比较以及一个分支。更有效的方法是重载
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
对于大多数迭代,它只需要一次比较,并且没有分支(请注意,分支可能会对性能产生不良影响)。请注意,我们不需要在命名空间
std
中定义它(这可能是非法的),但可以确保适当的using
指令使用正确的版本。这相当于在为某些用户定义类型专门swap()
时using std::swap;
。
关于性能,您的for
循环std::end
重复调用,而std::for_each
不会。这可能会导致也可能不会导致性能差异,具体取决于所使用的容器。
-
std::for_each
版本将只访问每个元素一次。 阅读代码的人只要看到std::for_each
就可以知道,因为在lambda中没有什么可以弄乱迭代器的。 在传统的 for 循环中,你必须研究循环的主体,以获得不寻常的控制流(continue
、break
、return
)和使用迭代器(例如,在这种情况下,用++it
跳过下一个元素)。 -
您可以轻松更改 lambda 解决方案中的算法。 例如,您可以创建一个访问每 n 个元素的算法。 在许多情况下,您实际上并不想要一个 for 循环,而是像
copy_if
这样的不同算法。 使用算法+lambda,通常更容易更改,并且更简洁。 -
另一方面,程序员更习惯于传统的for循环,所以他们可能会发现算法+lambda更难阅读。
首先,我看不出这两者之间有太大区别,因为for_each是使用 for 循环实现的。但请注意,for_each是一个具有返回值的函数。
其次,在这种情况下,我将使用范围循环语法,因为这一天无论如何很快就会到来。
确实; 在使用 Lambda 表达式的情况下,您必须声明参数类型和名称,因此不会赢得任何内容。
但是,一旦你想用这个调用一个(命名的)函数或函数对象,它就会很棒。(请记住,您可以通过std::bind
组合类似功能的东西。
Scott Meyers的书(我相信是Effective STL)很好地描述了这种编程风格。
- C++ 中用于二维数组的 for-each 循环
- 计算 fib(n) 的次数称为 FOR EACH n
- 支持自定义 const 本机C++容器类的"for each"
- C++ for-each 语句触发"vector iterators incompatible"断言失败:this->_Getcont() == 0
- 为什么我的矢量中的项目在 C++ 中的 for-each 循环期间不会改变?
- 当循环返回"for each"项的 nullptr 时?
- 正确使用C++ 'for each'选项
- "for each"能保证使用更改数组吗?
- 基于范围的循环与 for-each 循环有何不同
- C++:从"for each"循环中的映射中删除值
- 将函数应用于std::map值,其中包含for each和lambda函数
- 正在擦除for(-each)自动循环中的项目
- g++ 4.4.7 -std=gnu++0x 应该编译"for each"结构吗?
- 将循环转移到C++03 for each
- 在 for-each 循环中使用 'auto' 时是否需要添加'const'?
- C++中的"for each"循环如何知道数组的长度
- 基本c++: for-each循环
- 如何使c++中的for each循环函数与自定义类一起工作
- 如何使用for-each循环来美化析构函数
- for-each 循环生成错误,但 for(it=begin()...在遍历 std::map 包含 unique_ptr 时不会