C++ for 循环:条件评估

Posted

技术标签:

【中文标题】C++ for 循环:条件评估【英文标题】:C++ for loop: evaluation of condition 【发布时间】:2011-12-17 04:59:18 【问题描述】:

我有一个关于循环的(愚蠢的?)C/C++ 问题:

for (size_t i = 0; i < std::distance(begin, end); ++i) 
  a.push_back(i);

beginend 是两个迭代器。我的问题是,std::distance(begin, end) 是否为循环中的每个元素计算?还是使用这个版本更好:

size_t dist = std::distance(begin, end);
for (size_t i = 0; i < dist; ++i) 
  a.push_back(i);

【问题讨论】:

我不完全确定,但我认为第一个可能会慢得多。我认为编译器无法确定 std::distance(begin, end) 是常量,但我可能错了。 @quasiverse: 恐怕你错了:-S 感谢迭代器类别和特征检查,随机访问迭代器允许distance 的恒定时间实现。检查你的编译器的实现! 问题的简化和相关版本是在for (auto it = v.begin(); it != v.end(); ++it) 中是否在每一轮中都执行对v.end() 的调用。该标准要求程序的行为就像每次调用它,因此编译器只有在能够证明结果始终相同(并且没有副作用)的情况下才能对其进行优化. @Kerrel: if 它是一个随机访问迭代器。但 quasiverse 表示它可以显着变慢。例如,当开始/结束不是随机访问时,distance 是否被提升就变得相当重要。如果编译器不能这样做,那么程序员应该这样做。 请注意,如果需要,您可以在循环内限定额外变量:for (size_t i = 0, dist = std::distance(begin, end); i &lt; dist; ++i) 【参考方案1】:

编译器是否可以执行优化取决于(至少)所涉及的类型。例如,如果beginend是列表迭代器,a是列表,enda末尾的迭代器,那么这是一个无限循环(直到out-of-记忆发生)。如果改变了程序的含义,编译器就无法进行“优化”。大概它不会改变程序的含义,或者你不会问这个问题,但编译器仍然必须以某种方式排除这种可能性,例如通过跟踪 end 的值来设置它。在某些情况下,这对您来说可能很明显,但超出了编译器的能力来证明。

相反,如果beginend 是向量迭代器,那么在实践中它们要么是指针,要么是指针的薄包装器。然后编译器很可能会看到它们的值在循环中永远不会改变,因此它们的距离永远不会改变,并进行优化。但是,即使它没有进行优化,distance 对于向量迭代器来说也很便宜。因此,在这种情况下,优化可能并不那么重要。

相反,向量迭代器的检查实现在理论上可能包含代码,以查看自迭代器被采用以来向量是否已重新分配(因此迭代器不再有效)。这个事实可以在循环中改变,所以如果std::distance 间接调用该检查,那么在该实现上它不能被提升。并不是说您通常会将检查迭代器与优化结合起来,但这是可能的。

与以往一样,优化依赖于实现。如果您真的很在意,则需要查看您关心的编译器发出的代码。

对于它的价值,对于你的第二个 sn-p,有些人更喜欢这种风格:

for (size_t i = 0, dist = std::distance(begin, end); i < dist; ++i) 
  a.push_back(i);

它依赖于比较中的类型相同,但它避免将dist 放入不需要的周围范围内。

【讨论】:

【参考方案2】:

是的。第二个版本更好。

对于第一个版本,如果容器类型astd::vectorbeginenda的迭代器,那么push_back操作可能导致调整向量的大小,这反过来将使beginend 迭代器无效,并且在下一次迭代中使用它们来计算距离将调用未定义的行为。在这种情况下,第二个不仅更好,而且定义明确

【讨论】:

等等,谁说beginenda的迭代器? @KerrekSB:好点。我假设是这样:P。让我将这个假设添加到我的答案中。 我很确定迭代器实际上可以 not 指向有问题的容器,否则该循环将永远不会终止(除非 OP 实际上对这一点感到困惑,但我没有得到这个印象)。 @KerrekSB:也许,也许不是。我只是想添加这个作为记录。 @Kerrek:但是从优化 POV 来看,questioner 是否对这个问题感到困惑并不重要,重要的是 compiler 是否知道循环不是无限的!如果没有看到周围的代码,我们当然无法知道编译器知道什么。【参考方案3】:

很可能,这取决于编译器的优化。如果它们被关闭,std::distance 将在每个循环上执行,这是肯定的。原因 - 迭代器可能会在循环体内发生变化。

因此,如果您不更改迭代器,则更喜欢第二个版本,即使认为这是非常小的优化。 在大多数情况下,这是个人选择的问题(如果这不是瓶颈,这不太可能)。


编辑我的回答假设您的代码中的beginend 不是 beginend向量(或任何它是)a。如果是,那么您问题的答案取决于您实际尝试做什么。

【讨论】:

即使输入没有改变,返回的值也可能改变。是的,std::distance() 不会像这样工作,但编译器/优化器不能知道(通常)。 真的有足够智能的编译器能够意识到std::distance 的值是不变的吗? @Raedwald:当然,这取决于beginend 是什么。例如std::vector&lt;int&gt; v(5); std::vector&lt;int&gt;::const_iterator begin = v.begin(), end = v.end();,GCC 4.5.3 和 -O2,我在循环中得到add $0x1,%ebx // cmp $0x5,%ebx。它不仅意识到std::distance 结果是不变的,而且在编译时计算它。【参考方案4】:

两者都将具有相同的性能,因为任何体面的编译器都会将代码转换为第二个版本。如果关闭了编译器优化,请使用第二个版本。

【讨论】:

"任何体面的编译器都会将代码转换成第二个版本" 哪些编译器是“体面的”? 主观回答...但是任何能够正确执行基础知识并且擅长一般优化的编译器都是不错的编译器。现在,这些基本功能和一般优化的定义可能因人而异,这使得体面编译器的整个概念变得不那么主观了;) distance of list&lt;&gt;::iterator 不如 distance of vector&lt;&gt;::iterator 那样容易优化。【参考方案5】:

如果没有优化,它真的是每次都计算出来的。在优化的实践中,它不应该有所作为。如果不是每次都计算它是至关重要的(例如,因为这是你最内心要做的事情),你总是可以安全地使用第二个。

【讨论】:

即使进行了优化,它也应该有所作为...编译器不知道std::distance(begin, end) 将返回一个常量值,因此它必须检查每次迭代。 我对此不是 100% 确定的。我假设 std::distance to tget inlined 无论如何,进一步的优化可能能够检测到结果不可能改变。 "可能会" 可能会,但这取决于很多因素。【参考方案6】:

第二个版本更好。在第一个中,每次都会评估条件(不会自动假设结果是不变的)。

【讨论】:

以上是关于C++ for 循环:条件评估的主要内容,如果未能解决你的问题,请参考以下文章

循环条件中的三元运算符:评估顺序/操作。优先级不明确

我需要一种类似于for循环重新评估测试条件的行为的算法

For 循环和多个条件

使用条件 while/for 循环的指针在编译时会出错

C ++:for循环中的多个退出条件(多个变量):AND -ed或OR -ed?

for循环