比手写循环更喜欢算法?


10

您对以下哪项更具可读性?手写循环:

for (std::vector<Foo>::const_iterator it = vec.begin(); it != vec.end(); ++it)
{
    bar.process(*it);
}

或算法调用:

#include <algorithm>
#include <functional>

std::for_each(vec.begin(), vec.end(),
              std::bind1st(std::mem_fun_ref(&Bar::process), bar));

我想知道是否std::for_each真的值得,因为这样一个简单的示例已经需要大量的代码。

您对此事有何看法?


2
对于C ++,答案非常明显。但是在其他语言中,两者几乎相等(例如Python:,map(bar.process, vec)尽管不建议使用map来产生副作用,并且建议在map上使用列表理解/生成器表达式)。

5
然后还有BOOST_FOREACH……
本杰明·班尼尔

那么,有效的STL中的项目没有使您信服? stackoverflow.com/questions/135129/...
作业

Answers:


18

引入lambda是有原因的,这是因为即使标准委员会也认识到第二种形式很烂。使用第一种形式,直到获得C ++ 0x和lambda支持。


2
可以使用相同的推理来证明第二种形式的合理性:标准委员会认识到它的优势如此之大,以至于他们引入了lambda来使之更好。:-p(不过+1)
康拉德·鲁道夫

我不认为第二点那么糟糕。但这可能会更好。例如,通过不添加operator()故意使Bar对象变得更难使用。通过使用它,可以大大简化循环中的调用点并使代码整洁。
马丁·约克

注意:由于有两个主要投诉,增加了lambda支持。1)需要将参数传递给函子的标准样板。2)在呼叫点没有代码。您的代码只满足一个方法,而不能满足这些要求。所以1)没有用于传递参数的额外代码2)该代码已经不在调用点,因此lambda不会提高代码的可维护性。因此,lambda是一个很好的补充。对于他们来说,这不是一个好理由。
马丁·约克

9

始终使用最能描述您打算做什么的变量。那是

对于每一个元素xvec,这样做bar.process(x)

现在,让我们检查示例:

std::for_each(vec.begin(), vec.end(),
              std::bind1st(std::mem_fun_ref(&Bar::process), bar));

我们有一个for_each在那里,太- yippeh。我们拥有[begin; end)要操作的范围。

原则上,该算法更加明确,因此比任何手写实现都更可取。但是然后 ...粘合剂?Memfun?基本上C ++内部如何获取成员函数?对于我的任务,我不在乎它们!我也不希望遭受这种冗长而令人毛骨悚然的语法的困扰。

现在另一种可能性:

for (std::vector<Foo>::const_iterator it = vec.begin(); it != vec.end(); ++it)
{
    bar.process(*it);
}

当然,这是一种常见的模式,可以识别,但是...创建迭代器,循环,递增,取消引用。为了完成任务,我也不关心这些。

诚然,它看起来WAAY比第一个解决方案(至少,循环是灵活的,非常明确),但尽管如此,它不是真的太棒了。如果没有更好的可能性,我们将使用此方法,但也许我们有...

有更好的方法吗?

现在回到for_each。那岂不是巨大的字面上说for_each ,并在将要进行的操作是灵活的,太?幸运的是,由于C ++ 0x lambda,

for_each(v.begin(), v.end(), [&](const Foo& x) { bar.process(x); })

现在,我们已经找到了针对许多相关情况的抽象通用解决方案,值得注意的是,在这种特定情况下,绝对是#1的最爱

foreach(const Foo& x, vec) bar.process(x);

确实没有比这更清楚的了。幸运的是,C ++ 0x 内置了类似的语法!


2
表示“相似语法”为for (const Foo& x : vec) bar.process(x);,或根据const auto&需要使用。
乔恩·普迪

const auto&有可能吗 不知道-很棒的信息!
达里奥

8

因为这是如此难以理解?

for (unsigned int i=0;i<vec.size();i++) {
{
    bar.process(vec[i]);
}

4
抱歉,由于某种原因,它说“已删节”,我认为您的代码应该在哪里。
2011年

6
您的代码会向编译器发出警告,因为将a int与a 比较size_t是危险的。另外,例如,索引并非适用于每个容器std::set
fredoverflow 2011年

2
此代码仅适用于带有索引运算符的容器,该容器很少。它将算法不必要地束缚下来-如果map <>更合适怎么办?原始的for循环和for_each将不受影响。
JBRWilkinson '02

2
您为向量设计代码多少次,然后决定将其交换为地图?好像为此设计是语言优先。
马丁·贝克特

2
不鼓励按索引遍历集合,因为某些集合在索引编制方面的性能较差,而所有集合在使用迭代器时表现良好。
slaphappy 2012年

3

通常,无论他们有什么背景,几乎任何知道for循环的人都可以阅读第一种形式。

通常,第二个也不是那么容易理解:足够容易地了解for_each在做什么,但是如果您从未见过,std::bind1st(std::mem_fun_ref我可以想象很难理解。


2

实际上,即使使用C ++ 0x,我也不确定for_each会得到多少爱。

for(Foo& foo: vec) { bar.process(foo); }

是更具可读性的方式。

我不喜欢算法(在C ++中)的一件事是它们在迭代器上进行推理,使得语句非常冗长。


2

如果您将bar写为函子,那会简单很多:

// custom functor
class Bar
{    public: void operator()(Foo& value) { /* STUFF */ }
};


std::for_each(vec.begin(), vec.end(), Bar());

这里的代码可读性强。


2
除了您只是写了一个函子之外,它还很可读。
Winston Ewert'2

请参阅此处以了解为什么我认为此代码更糟糕。
2012年

1

我喜欢后者,因为它整洁干净。它实际上是许多其他语言的一部分,但在C ++中,它是库的一部分。没关系。


0

我声称,现在这个问题实际上并非特定于C ++。

问题是,将某个函子传递到另一个函数中并不意味着要替换循环
应该简化某些模式,这些模式在函数式编程中变得非常自然,例如visitorobserver,仅举两个例子,这些模式立即浮现在我的脑海。
在缺少一阶函数的语言中(Java可能是最好的例子),这种方法总是需要实现一些给定的接口,这是非常冗长和多余的。

我在其他语言中看到的很多常见用法是:

someCollection.forEach(someUnaryFunctor);

这样做的好处是,您不需要知道如何someCollection实际实施或做什么someUnaryFunctor。您需要知道的是,它的forEach方法将迭代集合的所有元素,并将它们传递给给定的函数。

就个人而言,如果您能在每个迭代步骤中获得有关要迭代的数据结构的所有信息以及有关要执行的操作的所有信息,那么功能性方法会使事情特别复杂,尤其是在语言方面,看起来很乏味。

同样,您应该记住,函数方法比较慢,因为您有很多调用,这些调用以一定的成本进行,而您没有for循环。


1
“此外,您应该记住,函数方法比较慢,因为您有很多调用,这些调用以一定的代价,而您不会在for循环中使用。” ?不支持此语句。功能方法不一定较慢,特别是因为可能在引擎盖下进行了优化。即使您完全理解循环,它也可能看起来更加抽象。
Andres F.

@Andres F:看起来更干净吗?std::for_each(vec.begin(), vec.end(), std::bind1st(std::mem_fun_ref(&Bar::process), bar));而且它要么在运行时变慢,要么在编译时变慢(如果幸运的话)。我不明白为什么用函数调用替换流控制结构会使代码更好。关于这里提出的任何替代方案,更具可读性。
back2dos

0

我认为这里的问题for_each不是真正的算法,只是编写手写循环的一种不同(通常是次等方式)。从这个角度来看,它应该是使用最少的标准算法,是的,您可能还应该使用for循环。但是标题中的建议仍然存在,因为有几种针对特定用途的标准算法。

如果有一种算法可以更严格地执行您想要的操作,那么是的,您应该更喜欢算法而不是手写循环。这里明显的例子是与分选sortstable_sort或搜索lower_boundupper_bound或者equal_range,但他们大多有一些情况是他们最好使用在手工编码循环。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.