对于stl容器,end()是一个昂贵的操作

时间:2012-03-19 10:21:05

标签: c++ performance stl containers

https://doc-snapshots.qt.io/qtcreator-extending/coding-style.html上,建议编写如下循环:

Container::iterator end = large.end();
for (Container::iterator it = large.begin(); it != end; ++it) {
        //...;
}

而不是

for (Container::iterator it = large.begin(); it != large.end(); ++it) {
        //...;
}

由于我很少在任何代码中看到这种风格,我想知道end()的连续调用是否真的为stl容器上的大型循环添加了明显的运行时开销,或者编译器是否已经优化了这种情况。 / p>

编辑: 正如许多非常好的评论所指出的那样:只有当循环中的代码不修改结束迭代器时,此问题才有效。否则,当然必须重复调用结束。

7 个答案:

答案 0 :(得分:18)

C ++ 11标准(第23.2.1节)要求end具有O(1)复杂度,因此符合标准的实现对两个版本都具有相同的性能特征。

那就是说,除非编译器能够证明end的返回值永远不会改变,否则将end拉出循环可能更快一些不变量(正如Steve Jessop评论的那样,有很多变量可以影响这是否真实。)

尽管如此,即使在一个特定情况下绝对没有性能差异,将这些测试拉出循环是一个很好的习惯。进入的甚至更好的习惯是使用标准算法,如@pmr所说,它完全回避了这个问题。

答案 1 :(得分:7)

这不是关于end成本高的问题,而是关于编译器看到end不会通过循环体中的副作用(它是循环不变量)而改变的能力。

标准要求end的复杂性保持不变。请参阅23.2.1中N3337中的表96。

使用标准库算法很好地避免了整个困境。

答案 2 :(得分:3)

如果您计划在迭代时修改集合,则必须以第二种方式进行修改(结束可以更改) - 否则第一种方法在理论上会快一点。我怀疑它会引人注意。

答案 3 :(得分:2)

实际上,end()方法是内联的。第二个不是每次都调用它,我不认为end()给出任何性能滞后。

答案 4 :(得分:0)

std :: vector.end()(for example)按值返回迭代器。在第二个循环中,您将在每个循环中创建一个对象。如果您不需要,编码标准告诉您不要创建对象。编译器可能是智能的,并为您优化代码,但这不是保证。更好的解决方案是使用stl算法。它们已经过优化,您的代码将更具可读性。请注意,只有在未修改集合时,这两个循环才是等效的。

P.S。很可能性能上的差异很小

答案 5 :(得分:0)

对于现在读这篇文章的人来说,这个问题在C ++ 11中已经变得有些问题了。

我不确定这种回答是否有资格作为答案,因为它实际上并没有解决问题的重点。但我确实认为指出这里提出的问题在C ++ 11程序员的实践中很少遇到是有效的,而且我肯定会在几年前发现这个响应很有用。因此,此响应针对的是只想了解迭代浏览STL容器中所有元素的最佳方式的读者(vectorlistdeque等等。)。

假设OP想要访问容器中的每个元素,我们可以通过编写range-based for loop轻松回避定义end是否比调用Container::end()足够快的整个问题:

Container container; // my STL container that has been filled with stuff

// (note that you can replace Container::value_type with the value in the container)

// the standard way
for (Container::value_type element : container) {
    // access each element by 'element' rather than by '*it'
}

// or, if Container::value_type is large
Container container; // fill it with something
for (Container::value_type& element : container) {
    //
}

// if you're lazy
Container container; // fill it with something
for (auto element : container) {
    //
}

OP已经询问在每次迭代时简单地比较itContainer::end()的简洁程度与声明变量end的性能之间的权衡以及在每个步骤中进行比较是值得的。由于基于范围的for循环提供了一个简单,易于编写和易于阅读的替代方案,在内部也会发生end迭代器,而不是在每一步都调用Container::end()方法,我们需要纠缠于这个问题的案例已经减少到有限的案例。

根据cppreference.com,基于范围的for循环将产生具有与以下相同副作用的代码:

{
  auto && __range = range_expression ; 
  for (auto __begin = begin_expr,
        __end = end_expr; 
      __begin != __end; ++__begin) { 
    range_declaration = *__begin; 
    loop_statement 
  } 
} 

答案 6 :(得分:-3)

取决于实施,但我认为end()没有给出那么多的性能滞后。