C++中的'for'循环与Qt的'foreach'有何区别?

57

在C++中,使用for循环还是Qt提供的foreach操作符更好(或更快)?例如下面这个条件:

QList<QString> listofstrings;

哪个更好?

foreach(QString str, listofstrings)
{
    //code
}
或者
int count = listofstrings.count();
QString str = QString();
for(int i=0;i<count;i++)
{
    str = listofstrings.at(i);
    //Code
}

8
如果您不打算修改foreach循环变量,建议使用const QString&代替——这对速度也有影响。 - swongu
这里是为什么你要小心包含const的解释:http://labs.qt.nokia.com/2009/01/23/iterating-efficiently/ - Jeff Allen
1
这个问题的重要补充是,您绝不能在非Qt容器中使用Qt foreach,最有可能进行深度复制,即使没有事先进行分析,这也不是您想要的东西。 - Predelnik
4
自 Qt 5.7 开始,foreach 宏已经被弃用,请参阅下面的答案以了解详情。 - ymoreau
11个回答

153

在大多数情况下,这并不重要。

StackOverflow上关于哪种方法更快的大量问题掩盖了一个事实,即在绝大多数情况下,代码大部分时间都是在等待用户操作。

如果你确实担心这个问题,可以自己对其进行剖析并根据发现的情况采取行动。

但我认为你很可能会发现,只有在处理大量元素时才会在最强烈的数据处理工作中出现这个问题。差别可能只有几秒钟,而且仅在处理大量元素时才会出现。

首先使你的代码能够运行起来,然后再考虑如何使其运行得更快(仅当你发现实际存在性能问题时)。

在完成功能并能够正确剖析之前花费的优化时间大都是浪费时间。


52
这个数字已经不再是一个笑话。我们应该编写一个答案机器人,它会寻找“哪个更快”,并自动回复:“对其进行性能分析”。 - JaredPar
2
请看http://stackoverflow.com/questions/771092/is-method-a-faster-than-method-b,让我们看看它是否能够幸存 :-) - paxdiablo
21
虽然这个简单的例子可能并不适用,但我想说的是,“错误的代码就像你所能得到的最不优化的代码”。 - Michael Burr
1
@paxdiablo,我非常不同意。你给出的答案是“如果你真的很在乎,为自己进行分析。”并不意味着它是一个好答案,只是因为你很幸运,OP没有首先让代码工作,并且进行了过早的优化。如果你真正回答了像下面几个人所做的那样的问题,然后再添加你的评论,你的回答将会非常棒。只是说如果你真的需要测试它,那么就不是一个答案。 - scigor
1
我在一个大市场工作,这个市场每个月销售数百万种商品。当我需要向政府生成报告时,速度的快慢非常重要。使用QT的foreach循环,生成报告需要4个小时。但是如果改用普通的for循环,只需要15分钟。因此,根据你的应用程序,速度差异可能是荒谬的。 - Dalton
显示剩余11条评论

25

首先,我想说我同意Pax的观点,速度可能并不重要。在可读性方面,foreach更加优秀,在98%的情况下足够使用。

当然,Qt开发人员已经研究过它,并进行了一些剖析: http://blog.qt.io/blog/2009/01/23/iterating-efficiently/

从中得出的主要教训是:在只读循环中使用const引用可以避免创建临时实例。无论你使用哪种循环方法,这也使循环的目的更明确。


19

实际上这并不重要。如果你的程序运行缓慢,很可能不是出现了这个问题。然而,需要注意的是,你并没有进行完全相等的比较。Qt的foreach与此更加相似(本例将使用QList<QString>):

for(QList<QString>::iterator it = Con.begin(); it != Con.end(); ++it) {
    QString &str = *it;
    // your code here
}

通过使用一些编译器扩展(如GCC的__typeof__)来获取传递的容器类型,该宏可以实现此功能。同时想象一下boost的BOOST_FOREACH在概念上非常相似。

你的示例之所以不公平,是因为非Qt版本增加了额外的工作。

你正在进行索引而不是真正的迭代。如果你使用的是具有非连续分配的类型(我怀疑QList<>可能是这种情况),那么由于代码必须计算第n个项的“位置”,索引将更加昂贵。

也就是说,这仍然不重要。这两个代码片段之间的时间差异将是微不足道的(如果存在的话)。不要浪费时间担心这个问题。写出你认为更清晰和易懂的代码。

编辑: 作为奖励,我目前强烈支持C++11版本的容器迭代,它干净,简洁且简单:

for(QString &s : Con) {
    // you code here
}

关于 at()operator[] 的性能问题,你的陈述并不正确,至少对于 Qt 而言是这样的。两者都会或不会执行边界检查,这取决于编译选项。在 Qt 中不使用异常。相反,文档 表明 "at() 可能比 operator[]() 更快,因为它永远不会导致深层复制发生"。 - Tim Hoffmann

18

1
请将此作为评论添加到问题中,更多人需要看到这个! - Gerhard Burger

13

我不想回答哪个更快的问题,但我想说哪个更好。

Qt的foreach最大的问题在于它在迭代之前会复制一份容器。你可能会说“这并不重要,因为Qt类是引用计数的”,但由于使用了副本,你实际上根本没有改变原始容器。

总之,Qt的foreach只能用于只读循环,因此应该避免使用。Qt会让你编写一个foreach循环,你认为它会更新/修改你的容器,但最终所有更改都将被丢弃。


我同意。在分析过程中,我遇到了一个foreach:结果发现我已经将QList更改为QVarLengthArray(再次进行分析后),并且发现foreach复制了我的QVarLengthArray。糟糕。我使用了常规的for循环,然后在分析器中不再显示该堆栈跟踪。 - Ben

4
首先,我完全同意“无关紧要”的答案。选择最干净的解决方案,并在出现问题时进行优化。
但另一种看待它的方式是,通常最快的解决方案是最准确地描述您意图的解决方案。在这种情况下,QT的foreach表示您想要为容器中的每个元素应用某些操作。
一个普通的for循环表示您想要一个计数器i。您想要反复将值i加1,并且只要它小于容器中的元素数量,您就想执行某些操作。
换句话说,普通的for循环过度说明了问题。它增加了许多实际上不属于您要做的事情的要求。您不在乎循环计数器。但是一旦编写for循环,它就必须存在。
另一方面,QT的开发人员没有做出可能影响性能的其他承诺。他们只保证迭代通过容器并对每个元素应用动作。
换句话说,通常最干净和最优雅的解决方案也是最快的。

3

3
Qt的foreach循环语法在for循环方面更加清晰易懂,因此从这个意义上讲更好。就性能而言,我怀疑其中是否有任何区别。你可以考虑使用BOOST_FOREACH,因为它是一个经过深思熟虑的花式循环,并且具有可移植性(并且可能会成为C++的一部分,也具有未来的保障)。

2

对于小集合,使用for和foreach都可以,但foreach更加清晰易懂。

然而,对于较大的集合,for循环会在某个点上开始胜过foreach循环。(假设'at()'运算符是高效的)

如果这真的很重要(我认为你问了这个问题就说明它很重要),那么最好的方法是进行测量。可以使用分析器,或者您可以构建一个带有一些仪器的测试版本。


0

我预计在某些情况下foreach会比其他情况下更快,除了对于实际上是数组的项目,此时性能差异可以忽略不计。

如果它是基于枚举器实现的,相对于直接索引来说,它可能更高效,这取决于具体实现。但不太可能效率低下。例如,如果有人将一个平衡树同时作为可索引和可枚举显示,那么foreach将会更快。这是因为每个索引都必须独立地查找引用的项,而枚举器则具有更高效地导航到下一个节点的当前节点的上下文。

如果你有一个实际的数组,那么就要取决于语言和类的实现,无论是foreach还是for,哪一个更快。

如果索引是字面内存偏移量(例如C ++),那么由于避免了函数调用,for应该会稍微更快。如果索引是间接的,就像一次调用一样,则应该是相同的。

话虽如此,我发现很难找到一种普遍性的情况。即使应用程序存在性能问题,这也是您应该寻找的最后一种优化方式。如果您有一个可以通过更改迭代方式来解决的性能问题,那么您实际上并没有性能问题。你有一个BUG,因为有人编写了一个非常烂的迭代器或者索引器。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接