std::for_each
over for
loop 有什么优点吗?在我std::for_each
看来,这似乎只会阻碍代码的可读性。那么为什么有些编码标准建议使用它呢?
std::for_each
over for
loop 有什么优点吗?在我std::for_each
看来,这似乎只会阻碍代码的可读性。那么为什么有些编码标准建议使用它呢?
Answers:
这种做法的好处与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
循环更多。
Element & e
至 不会使用auto & e
(或auto const &e
)看起来更好。Element const e
当我想要隐式转换时,例如当源是不同类型的集合,并且希望它们转换为时,我会使用(无参考)Element
。
原因如下:
似乎是因为您不习惯和/或没有使用正确的工具使其变得简单而导致可读性下降。(有关帮助程序,请参见boost :: range和boost :: bind / boost :: lambda。其中许多将进入C ++ 0x,并使for_each和相关函数更有用。)
它允许您在for_each之上编写可与任何迭代器一起使用的算法。
它减少了愚蠢的键入错误的机会。
它也打开你的心灵的STL的算法,休息一下就好了find_if
,sort
,replace
等这些不会看起来那么奇怪了。这可能是一个巨大的胜利。
更新1:
最重要的是,它可以帮助您超越for_each
for.loop之类的条件,并查看其他STL-alog,例如find / sort / partition / partition / copy_replace_if,并行执行..等等。
使用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-算法单层包装器,该包装器可用于范围而不是一对迭代器。boost :: range_ex一旦发布,将包含该内容,也许它也将在C ++ 0x中存在?
outer_class::inner_class::iterator
或者他们是模板参数:typename std::vector<T>::iterator
...在对结构本身能碰上本身是一个多线结构
for_each
第二个例子是不正确的(应该是for_each( bananas.begin(), bananas.end(),...
for_each
更通用。您可以使用它来遍历任何类型的容器(通过传递begin / end迭代器)。您可以潜在地在使用的函数下换出容器,for_each
而不必更新迭代代码。您需要考虑的是,除了std::vector
普通的C数组之外,世界上还有其他容器才能看到的优势for_each
。
的主要缺点for_each
是它需要一个函子,因此语法很笨拙。这在C ++ 11(以前为C ++ 0x)中通过引入lambda修复:
std::vector<int> container;
...
std::for_each(container.begin(), container.end(), [](int& i){
i+= 10;
});
3年后,这对您来说似乎并不奇怪。
for ( int v : int_vector ) {
(即使它今天可以模拟与BOOST_FOREACH)
std::for_each(container, [](int& i){ ... });
。我的意思是为什么一个人被迫两次写容器?
container.each { ... }
而没有提及开始和结束迭代器。我发现必须一直指定结束迭代器有点多余。
就个人而言,任何时候我都需要竭尽全力地使用std::for_each
(编写专用函子/ complex boost::lambda
s),我就会发现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)));
像许多算法功能一样,最初的反应是认为使用foreach比循环更难读。这是许多火焰战争的主题。
一旦习惯了这种习语,您可能会发现它很有用。一个明显的优点是,它迫使编码器将循环的内部内容与实际的迭代功能分开。(好的,我认为这是一个优势。换句话说,您只是在切分代码而没有实际好处)。
另一个优点是,当我看到foreach时,我知道要么将处理每个项目,要么将引发异常。
一个用于循环允许用于终止循环的几个选项。您可以让循环运行整个过程,也可以使用break关键字显式跳出循环,或者使用return关键字退出整个函数中期循环。相反,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;
}
}
}
std::for_each()
旧标准时(在撰写本文时),您必须使用一个命名函子,该函子可以像您所说的那样提高可读性,并禁止过早地打破循环。但是,等效for
循环只不过是一个函数调用,因此也禁止过早中断。但是除此之外,我认为您在说std::for_each()
强制执行遍及整个范围方面非常有意思。
您大体上是对的:大多数时候,std::for_each
都是净亏损。我竟然比去for_each
来goto
。goto
提供最通用的流控制-您可以使用它来实现您可以想象的几乎任何其他控制结构。这非常的多功能性,但是,意味着看到一个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"; }
};
而他们的职位是问什么组合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"));
这不工作-并且需要在几乎所有没有工作弄清楚,它打印的内容coll
来std::cout
。
boost::mem_fn(&XXX::print)
而不是XXX::print
std::cout
它作为其工作的论据)。
编写函数以提高可读性的优势可能在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循环概括为一个行函数=)
简便:for_each
当您已经具有处理每个数组项的功能时很有用,因此您不必编写lambda。当然,这
for_each(a.begin(), a.end(), a_item_handler);
胜过
for(auto& item: a) {
a_item_handler(a);
}
同样,远程for
循环从头到尾仅遍历整个容器,同时for_each
更加灵活。
所述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
(在下一个标准中的lambda)使其更简单:
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(同事让我参与其中)
除了可读性和性能之外,一致性经常被忽略。有多种方法可以通过以下方式在迭代器上实现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循环。
我曾经不喜欢std::for_each
并以为没有lambda,那是完全错误的。但是前段时间我确实改变了主意,现在我真的很喜欢它。而且我认为它甚至可以提高可读性,并使其更容易以TDD方式测试代码。
该std::for_each
算法可以被读取,就像所有元素都在range范围内一样,可以提高可读性。假设您要执行的动作长20行,执行该动作的功能也大约20行长。这将使一个函数使用常规的for循环长40行,而使用则仅大约20行std::for_each
,因此可能更容易理解。
的函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循环相比,编译器可能会产生更好的代码,因为(for_each)对于编译器来说总是一样的,并且编译器作者可以投入所有知识,使其与他们一样好。能够。
同样适用于其他标准算法,例如find_if
,transform
等等。
for
是用于可以迭代每个元素或每三个元素等等for_each
的循环。仅用于迭代每个元素。从它的名字很明显。因此,您将更清楚地打算在代码中执行什么操作。
++
。可能不寻常,但是for循环也是如此。
transform
不要混淆别人。
如果您经常使用STL中的其他算法,则有以下几个优点for_each
:
与传统的for循环不同,它for_each
迫使您编写适用于任何输入迭代器的代码。以这种方式受到限制实际上可能是一件好事,因为:
for_each
。for_each
有时使用显然可以使用更特定的STL函数来执行相同的操作。(就像杰里·科芬的例子一样;for_each
最好的选择不一定是这种情况,但是for循环并不是唯一的选择。)
使用C ++ 11和两个简单的模板,您可以编写
for ( auto x: range(v1+4,v1+6) ) {
x*=2;
cout<< x <<' ';
}
作为替换for_each
或循环。为什么选择它归结为简洁和安全起见,如果表达式不存在,就不会出错。
对我来说,for_each
当循环主体已经是函子时,基于相同的理由总会更好,并且我将尽我所能获得的优势。
您仍然使用3表达式for
,但是现在当您看到一个3表达式时,您知道那里需要理解的东西,不是样板。我讨厌样板。我很讨厌它的存在。这不是真正的代码,阅读本书没有什么可学的,只是需要检查的一件事。可以通过检查生锈的容易程度来衡量其精神努力。
模板是
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); }
通常,您必须遍历整个集合。因此,我建议您编写仅包含2个参数的for_each()变体。这将使您可以将Terry Mahaffey的示例重写为:
for_each(container, [](int& i) {
i += 10;
});
我认为这确实比for循环更具可读性。但是,这需要C ++ 0x编译器扩展。
我发现for_each对可读性不利。这个概念很好,但是c ++使得很难编写可读性,至少对我而言。c ++ 0x lamda表达式将有所帮助。我真的很喜欢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循环清除器可以读取,并且显式地将命名变量用于start和end迭代器,从而减少了for循环中的混乱情况。
当然情况会有所不同,这正是我通常最能找到的。
您可以让迭代器成为对循环中每次迭代执行的函数的调用。
参见此处:http : //www.cplusplus.com/reference/algorithm/for_each/
for_each
做什么,在这种情况下,它没有回答有关其优势的问题。
for循环可能会中断;我不想成为Herb Sutter的鹦鹉,所以这是他的演讲的链接:http : //channel9.msdn.com/Events/BUILD/BUILD2011/TOOL-835T 请务必也阅读评论:)
for_each
允许我们实现Fork-Join模式。除此之外,它还支持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);
std::for_each
当与一起使用boost.lambda
或boost.bind
通常可以提高可读性时