【问题标题】:c++ for loop temporary variable usec++ for循环临时变量使用
【发布时间】:2010-11-05 18:00:41
【问题描述】:

以下哪个更好,为什么? (特别是c++)

一个。

int i(0), iMax(vec.length());//vec is a container, say std::vector
for(;i < iMax; ++i)
{
  //loop body
}

b.

for( int i(0);i < vec.length(); ++i)
{
  //loop body
}

由于调用了长度函数,我已经看到了 (a) 的建议。这让我很困扰。没有任何现代编译器将(b)的优化与(a)相似吗?

【问题讨论】:

  • 应该把上面的改成 vec.size()
  • 非常有趣的答案。令人惊讶的是,简单的问题可以成为有关堆栈溢出的良好教育的来源。
  • @Amod:请看 Andrew Shepherd 的回答:您提供的两段代码不等价!
  • 示例“b”中的条件基于“悬空假设”! :)

标签: c++ for-loop


【解决方案1】:

这里有两个问题需要讨论:

  1. 变量范围
  2. 结束条件重新评估

变量范围

通常,您不需要循环变量在循环之外可见。这就是为什么您可以在 for 构造中声明它的原因。

结束条件重新评估

Andrew Shepherd 说得很好:将函数调用放在结束条件中意味着不同:

for( vector<...>::size_type i = 0; i < v.size(); ++i ) { // vector size may grow.
   if( ... ) v.push_back( i ); // contrived, but possible
}

// note: this code may be replaced by a std::for_each construct, the previous can't.
for( vector<...>::size_type i = 0, elements = v.size(); i != elements; ++i ) {
}

【讨论】:

    【解决方案2】:

    为什么不用BOOST_FOREACH 完全回避这个问题

    #include <boost/foreach.hpp>
    
    std::vector<double> vec;
    
    //...
    
    BOOST_FOREACH( double &d, vec)
    {
        std::cout << d;
    }
    

    【讨论】:

      【解决方案3】:

      需要注意的是迭代器的例子:

      for (vector<T>::iterator it=vec.begin();it!=vec.end();it++){
       //loop body
      }
      

      如果循环体导致向量重新分配,可能会使循环迭代器“it”无效。因此它不等同于

      for (int i=0;i<vec.size();++i){
       //loop body
      }
      

      其中循环体向 vec 添加元素。

      【讨论】:

        【解决方案4】:

        有点不相关:

        警告:有符号和无符号整数之间的比较。

        数组和向量索引的正确类型是 size_t。

        严格来说,在 C++ 中甚至是std::vector&lt;&gt;::size_type。

        令人惊讶的是,仍有多少 C/C++ 开发人员犯了这个错误。

        【讨论】:

        • 是的 - 特别是因为正确答案是 vector::size_type 而不是 size_t。
        • 严格来说,是的,你是对的。但我已经很高兴看到 size_t 了,而且教育人们使用 vector&lt;&gt;::size_type 的机会……微不足道。
        • 使用 vector::size_type 而不是 size_t 的价值也可以忽略不计。它仅在您已经在使用模板容器的情况下有用(在这种情况下 container_type::size_type 比 size_t 更明确)。
        【解决方案5】:

        (b) 不会每次都计算/调用函数。

        --开始摘录----

        循环不变代码运动: GCC 包括循环不变代码运动作为其循环优化器的一部分以及部分冗余消除通道。这种优化会从循环中移除指令,这些指令会计算一个在循环的整个生命周期内都不会改变的值。

        --- 结束摘录--

        gcc 的更多优化:

        https://www.in.redhat.com/software/gnupro/technical/gnupro_gcc.php3

        【讨论】:

          【解决方案6】:

          让我们看看生成的代码(我使用的是经过全面优化的 MSVS 2008)。

          一个。

          int i(0), iMax(vec.size());//vec is a container, say std::vector
          for(;i < iMax; ++i)
          {
            //loop body
          }
          

          for 循环产生 2 条汇编指令。

          b.

          for( int i(0);i < vec.size(); ++i)
          {
            //loop body
          }
          

          for 循环产生 8 条汇编指令。 vec.size() 内联成功。

          c.

          for (std::vector<int>::const_iterator i = vec.begin(), e = vec.end(); i != e; ++i)
          {
            //loop body
          }
          

          for循环产生15条汇编指令(一切都是内联的,但代码有很多跳转)

          因此,如果您的应用程序对性能至关重要,请使用 a)。否则为 b) 或 c)。

          【讨论】:

          • 有趣。如何知道汇编指令细节?
          • 除了在c中,在vec中访问元素的汇编指令比在a或b中要少。
          • 在 stackoverflow 本身上得到了答案。 stackoverflow.com/questions/840321/…
          • 汇编指令的数量通常无关紧要。现代 CPU 重新映射指令。它们在访问主存时也有带宽限制。由于所有循环都访问相同的元素,因此它们都可能达到相同的限制。
          • 现代 CPU 可以重新映射指令但不能跳过它们。 CPU缓存可以减少内存带宽限制的影响。但我同意你的看法,在大多数情况下,a)、b) 和 c) 之间没有显着差异。
          【解决方案7】:

          我很惊讶没有人说明显的:

          在 99.99% 的情况下,这无关紧要。

          除非您使用的容器计算size() 是一项昂贵的操作,否则您的程序甚至会慢几纳秒是难以想象的。我会说坚持使用更具可读性,直到您分析您的代码并发现 size() 是一个瓶颈。

          【讨论】:

          • 这意味着不同的东西!所以它确实很重要!
          • 在 99.99% 的情况下,这无关紧要。只有 (a) size() 在循环内发生变化,或 (b) 检查 size() 速度过慢时才重要。
          【解决方案8】:

          这个比较好:

          typedef vector<int> container; // not really required,
                                         // you could just use vector<int> in for loop
          
          for (container::const_iterator i = v.begin(); i != v.end(); ++i)
          {
              // do something with (*i)
          }
          
          • 我可以马上看出向量 没有更新
          • 任何人都知道发生了什么 这里
          • 我知道有多少循环
          • v.end() 返回指针后一个 最后一个元素,所以没有开销 检查大小
          • 易于更新为不同 容器或值类型

          【讨论】:

            【解决方案9】:

            示例(b)与示例(a)的含义不同,编译器必须在您编写时对其进行解释。

            如果,(出于某种我想不出的虚构原因),我编写了代码来执行此操作:

            for( int i(0);i < vec.length(); ++i)
            {
                if(i%4 == 0)
                   vec.push_back(Widget());
            }
            

            我真的不希望编译器优化对 vec.length() 的每个调用,因为我会得到不同的结果。

            【讨论】:

            • 完美答案。您可能要补充一点,如果您不更改循环内向量的长度,则应该首选问题中的构造 (a)(或其他答案中的一些类似建议),因为编译器很难优化对 length() 的调用
            • 编译器优化与否如果您使用带有 begin() - end() 的迭代器,您将获得一次结束指针,您无需担心大小和所有...
            【解决方案10】:

            除非您需要循环外的循环变量,否则第二种方法更可取。

            迭代器实际上会给你同样好的或更好的性能。 (comp.lang.c++ 上有一个很大的比较线程。几年前进行了审核)。

            另外,我会使用

            int i = 0;
            

            而不是您正在使用的构造函数之类的语法。虽然有效,但它不是惯用的。

            【讨论】:

            • 自从我广泛使用 c++ 以来,我习惯于以这种方式初始化几乎任何东西?这是一个坏习惯吗?
            • @Amol:这是一个完全不同的讨论。无论如何都不错。
            • @nsanders:很高兴你提到了变量作用域!
            【解决方案11】:

            它为什么困扰着你? 这两个替代方案看不到做同样的事情。一种是进行固定次数的迭代,另一种是依赖于循环体。

            另一种选择是

            for (vector<T>::iterator it=vec.begin();it!=vec.end();it++){
             //loop body
            }
            

            【讨论】:

            • 和其他答案类似,你也可以使用:for (vector::iterator it = vec.begin(), end = vec.end(); it != end; + +it) { 进程(*it); }
            • 此外,一旦归结为单个函数:std::for_each(vec.begin(), vec.end(), process);
            【解决方案12】:

            编译器很难在知道它是常量的情况下提升vec.length() 调用,除非它被内联(希望它经常这样做!)。但至少i 绝对应该在第二种样式“b”中声明,即使length 调用需要“手动”吊出循环!

            【讨论】:

            • 这对编译器来说非常困难,但分支预测器会处理它。
            【解决方案13】:

            我喜欢:

            for (int i = 0, e = vec.length(); i != e; ++i)
            

            当然,这也适用于迭代器:

            for (vector<int>::const_iterator i = v.begin(), e = v.end(); i != e; ++i)
            

            我喜欢这样,因为它既高效(只需调用一次 end()),也相对简洁(只需键入一次 vector&lt;int&gt;::const_iterator)。

            【讨论】:

            • 这种方法对我来说似乎更具可读性。它是否记录在任何书籍中?哪一个?我是第一次在 stackoverflow 上阅读这篇文章。
            • 不,end 被多次调用,但由于调用是内联的,这无关紧要。
            • @Konrad,你为什么说 end 在第二个例子中不止一次被调用?你认为它被调用了多少次(以及为什么)?
            • 在循环中调用 i != v.end() 有什么问题?它可能是内联的,并且 end() 返回数组末尾的指针 ...
            • @stefan,是的,但他们也可能不是。在任何情况下,不管编译器优化如何,为什么不以您确定最有效的方式编写它?
            【解决方案14】:

            简单的问题:你是否在循环中修改vec?

            回答这个问题也会得到你的答案。

            jrh

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2013-06-20
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-04-05
              • 1970-01-01
              • 2015-12-26
              相关资源
              最近更新 更多