题
我似乎可以看到更多的'为'循环迭代在的问题和答案在这里,比我做for_each()、变换()等。斯科特*梅尔建议, stl算法是优选的, 或者至少他没有在2001年。当然,使用他们常常意味着运动的循环机构成一个功能或功能物体。有些人可能觉得这是一种不可接受的并发症,而其他人可能觉得更好休息下来的问题。
所以...应该STL算法是首选手卷循环?
解决方案
它取决于:
- 是否高效能是需要的
- 可读性循环
- 是否是复杂的算法
如果环路不是瓶颈,算法是简单的(如for_each),然后对当前的C++标准,我会更喜欢一个手卷循环的可读性。(局部性的逻辑是关键。)
但是,现在,C++0x/C++11是支持通过一些主要编译器,我想说的使用STL算法,因为他们现在允许lambda表达的--因此当地的逻辑。
其他提示
我将在这里反对谷歌并提倡使用STL算法和仿函数使代码更容易理解和维护,但你必须正确行事。你必须更加注意可读性和清晰度。特别是,你必须得到正确的命名。但是当你这样做时,你可以得到更清晰,更清晰的代码,并且范式转变为更强大的编码技术。
让我们举一个例子。在这里,我们有一群孩子,我们想要设置他们的“ Foo Count”一些价值。标准的for循环迭代器方法是:
for (vector<Child>::iterator iter = children.begin();
iter != children.end();
++iter)
{
iter->setFooCount(n);
}
哪个,是的,它非常清楚,绝对不是坏代码。只需稍微看一下就可以搞清楚。但是看看我们可以用适当的仿函数做些什么:
for_each(children.begin(), children.end(), SetFooCount(n));
哇,这正是我们所需要的。你不必弄明白;你马上知道它设置了&#8220; Foo Count&#8221;每个孩子(如果我们不需要.begin()/ .end()废话,那就更清楚了,但你不能拥有一切,而且在制作STL时他们也没有咨询过我。 )
当然,你确实需要定义这个神奇的仿函数, SetFooCount
,但它的定义很漂亮:
class SetFooCount
{
public:
SetFooCount(int n) : fooCount(n) {}
void operator () (Child& child)
{
child.setFooCount(fooCount);
}
private:
int fooCount;
};
总的来说,还有更多的代码,你必须查看另一个地方才能确切了解 SetFooCount
正在做什么。但是因为我们很好地命名,99%的时间我们不必查看 SetFooCount
的代码。我们假设它完成它所说的,我们只需要查看 for_each
行。
我真正喜欢的是使用算法会导致范式转换。您不必将列表视为对象集合,而是对列表的每个元素执行操作,而是将列表视为第一类实体,并直接在列表本身上操作。 for循环遍历列表,在每个元素上调用成员函数来设置Foo Count。相反,我正在做一个命令,它设置列表中每个元素的Foo Count。它很微妙,但当你看到森林而不是树木时,你会获得更多的力量。
因此,通过一点点思考和仔细命名,我们可以使用STL算法来制作更清晰,更清晰的代码,并开始在不太精细的层面上思考。
std :: foreach
是几年前让我诅咒STL的代码。
我不能说它是否更好,但我更喜欢将循环的循环代码放在循环前导码中。对我来说,这是强烈要求。并且 std :: foreach
构造将不允许我(奇怪的是,就我而言,Java或C#的foreach版本很酷......所以我猜它证实了我,循环体的局部性非常重要。)
因此,只有当只有一个可读/可理解的算法可用时,我才会使用foreach。如果没有,不,我不会。但这是一个品味问题,我想,因为我应该更加努力地去理解并学会解析所有这些......
请注意,升级的人显然感觉有点相同,因为他们写了BOOST_FOREACH:
#include <string>
#include <iostream>
#include <boost/foreach.hpp>
int main()
{
std::string hello( "Hello, world!" );
BOOST_FOREACH( char ch, hello )
{
std::cout << ch;
}
return 0;
}
请参阅: http://www.boost.org/ DOC /库/ 1_35_0 / DOC / HTML / foreach.html
这真是Scott Meyers出错的一件事。
如果存在与您需要做的匹配的实际算法,那么当然要使用该算法。
但是如果你需要做的就是遍历一个集合并对每个项目做一些事情,那么只需执行正常的循环而不是尝试将代码分离到一个不同的函数中,这样就最终会将代码分成几个部分而不需要任何代码。真正的收获。
还有一些其他的选项,比如boost :: bind或boost :: lambda,但那些是非常复杂的模板元编程的东西,它们在调试和单步执行时不能很好地工作,因此通常应该避免它们。
正如其他人所提到的,当lambda表达式成为一等公民时,这一切都会改变。
for
循环是必不可少的,算法是声明性的。当您编写 std :: max_element
时,它显然是您所需要的,当您使用循环来实现同样的目标时,它不一定如此。
算法也可以有轻微的性能优势。例如,当遍历 std :: deque
时,专门的算法可以避免冗余地检查给定的增量是否将指针移动到块边界上。
但是,复杂的仿函数表达式很快会使算法调用无法读取。如果显式循环更易读,请使用它。如果可以在没有十层绑定表达式的情况下表达算法调用,则无论如何都更喜欢它。可读性比 here 更重要,因为这种优化是Knuth所着名的Hoare;一旦你意识到它是一个瓶颈,你就能够毫无困难地使用另一个构造。
这取决于,如果算法不采用仿函数,那么总是使用std算法版本。这对你来说既简单又清晰。
对于采用仿函数的算法,通常不会,直到可以使用C ++ 0x lambdas。如果算子很小并且算法很复杂(大多数不是),那么仍然可以使用std算法。
我是主要的STL算法的忠实粉丝,但在实践中它太麻烦了。当你定义你的仿函数/谓词类时,两行for循环可以变成40多行代码,突然变得难以理解10倍。
值得庆幸的是,使用lambda函数, auto
和
语法的C ++ 0x,事情将变得更容易一吨。在维基百科上查看此 C ++ 0x概述。
我不会使用严格的规则。有许多因素要考虑,比如经常在代码中执行某些操作,只是循环或“实际”操作。算法,算法是否依赖于您必须传输到函数的大量上下文?
例如,我不会像
那样for (int i = 0; i < some_vector.size(); i++)
if (some_vector[i] == NULL) some_other_vector[i]++;
进入一个算法,因为它会导致更多的代码百分比明智,我将不得不处理以某种方式知道算法已知some_other_vector。
还有很多其他的例子,使用STL算法很有意义,但你需要根据具体情况来决定。
我认为STL算法接口是次优的并且应该避免,因为直接使用STL工具包(对于算法)可能在性能上给出非常小的增益,但肯定会花费当您学习如何使用这些工具时,可读性,可维护性,甚至可写性。
对于矢量循环的标准有多高效:
int weighted_sum = 0;
for (int i = 0; i < a_vector.size(); ++i) {
weighted_sum += (i + 1) * a_vector[i]; // Just writing something a little nontrivial.
}
比使用for_each构造,或者试图将其调整为累积调用?
你可能会认为迭代过程的效率较低,但是__每个步骤都会在每一步都引入一个函数调用(通过尝试内联函数可以减轻 ,但请记住“ ; inline“只是对编译器的建议 - 它可能会忽略它。)
无论如何,差异很小。根据我的经验,您编写的代码中超过90%是 而非性能至关重要,但是编码时间关键。通过保持您的STL循环所有字面内联,它是非常可读的。对于自己或未来的维护者来说,旅行的间接性较小。如果它在你的风格指南中,那么你为你的程序员节省了一些学习时间(承认,学习如何正确使用STL第一次涉及一些问题)。最后一点是我的意思是可写性成本。
当然有一些特殊情况 - 例如,您可能实际上想要将for_each函数分开以在其他几个地方重复使用。或者,它可能是少数高性能关键部分之一。但这些都是特殊情况 - 例外而不是规则。
当然,在函数和类中隐藏细节都是抽象的一部分,一般来说,库抽象比重新发明轮更好。但抽象的关键技能是知道何时做 - 以及当不时这样做。过度抽象可能会损害可读性,可维护性等。良好的判断力来自经验,而不是来自不灵活的规则 - 当然,在学会破解规则之前必须学习规则。
OTOH,值得考虑的事实是很多程序员已经使用C ++(以及之前的C,Pascal等)很长一段时间。旧习惯很难改变,有一种叫做认知失调的东西常常导致借口和理性化。但不要妄下结论 - 这至少与标准人员犯后决策失误一样可能。我认为一个重要因素是开发人员的舒适度。
使用transform或for_each是正确的做法可能是正确的,但它并不是更有效,手写循环本身并不危险。如果开发人员编写一个简单的循环需要半个小时,而半天才能获得transform或for_each的语法,并将提供的代码移动到函数或函数对象中。然后其他开发人员需要知道发生了什么。
通过学习使用transform和for_each而不是手工制作的循环,新开发者可能会得到最好的服务,因为他能够始终如一地使用它们而不会出错。对于我们其余的人而言,编写循环是第二天性,最好坚持我们所知道的,并在业余时间更熟悉算法。
这样说 - 如果我告诉我的老板我花了一天时间将手工制作的循环转换为for_each并转换电话,我怀疑他会非常高兴。