const引用临时值与返回值优化

时间:2012-11-10 01:15:04

标签: c++ optimization return-value-optimization temporary-objects const-reference

我意识到将一个右值赋值给一个const左值引用会延长临时值的生命周期,直到范围结束。但是,我不清楚何时使用它以及何时依赖返回值优化。

LargeObject lofactory( ... ) {
     // construct a LargeObject in a way that is OK for RVO/NRVO
}

int main() {
    const LargeObject& mylo1 = lofactory( ... ); // using const&
    LargeObject mylo2 = lofactory( ... ); // same as above because of RVO/NRVO ?
}

根据Scot Meyers的说法'更有效的C ++(第20项)第二种方法可以由编译器优化来构建适当的对象(这将是理想的,并且正是人们试图在第一种方法中使用const&实现的。)

  1. 在临时使用const&以及何时依赖RVO / NRVO时,是否有任何普遍接受的规则或最佳做法?
  2. 是否存在使用const&方法比不使用方法更糟糕的情况? (如果LargeObject实现了那些,那我就想一想关于C ++ 11移动语义的例子......)

2 个答案:

答案 0 :(得分:13)

让我们考虑最简单的案例:

lofactory( ... ).some_method();

在这种情况下,可以从 lofactory 到调用者上下文的一个副本 - 但可以通过 RVO / NRVO 对其进行优化。


LargeObject mylo2 ( lofactory( ... ) );

在这种情况下,可能的副本是:

  1. 临时 lofactory 返回给来电者上下文 - 可以通过 RVO / NRVO
  2. 进行优化
  3. 来自临时的复制构建 mylo2 - 可以通过 copy-elision
  4. 进行优化
    const LargeObject& mylo1 = lofactory( ... );
    

    在这种情况下,仍然可以使用一个副本:

    1. 临时 lofactory 返回到来电者上下文 - 可以通过 RVO / NRVO 进行优化(也是!)
    2. 引用将绑定到此临时。


      所以,

        

      使用const& amp;是否有任何普遍接受的规则或最佳实践对临时工和何时依赖RVO / NRVO?

      如上所述,即使在const&的情况下,也可以使用不必要的副本,并且可以通过 RVO / NRVO 对其进行优化。

      如果您的编译器在某些情况下应用 RVO / NVRO ,那么很可能它会在第2阶段(上面)执行copy-elision。因为在这种情况下,复制省略比NRVO简单得多。

      但是,在最坏的情况下,您将获得const&案例的一个副本,以及初始化值时的两个副本。

        

      是否存在使用const& amp;方法比不使用它更差?

      我不认为有这种情况。至少除非您的编译器使用区分const&的奇怪规则。 (有关类似情况的示例,我注意到MSVC不进行NVRO进行聚合初始化。)

        

      (我想的是关于C ++ 11移动语义的例子,如果LargeObject实现了那些......)

      在C ++ 11中,如果LargeObject具有移动语义,那么在最坏的情况下,您将为const&情况进行一次移动,并在启动该值时进行两次移动。所以,const&仍然有点好。


        

      所以一个好的规则就是永远将临时工具绑定到const&如果可能,因为如果编译器由于某种原因无法进行复制省略,它可能会阻止复制?

      在不知道实际应用背景的情况下,这似乎是一个很好的规则。

      在C ++ 11中,可以将临时绑定到右值引用 - LargeObject&&amp ;.所以,这样的临时性可以修改。


      顺便说一句,移动语义仿真可以通过不同的技巧提供给C ++ 98/03。例如:

      然而,即使存在移动语义 - 也存在不能廉价移动的对象。例如,4x4矩阵类,内部有双数据[4] [4]。因此,即使在C ++ 11中,Copy-elision RVO / NRVO仍然非常重要。顺便说一句,当Copy-elision / RVO / NRVO发生时 - 它比移动更快。


      P.S。,在实际情况下,还应考虑一些其他事项:

      例如,如果你有返回向量的函数,即使应用了Move / RVO / NRVO / Copy-Elision - 它仍然可能不是100%有效。例如,请考虑以下情况:

      while(/*...*/)
      {
          vector<some> v = produce_next(/* ... */); // Move/RVO/NRVO are applied
          // ...
      }
      

      将代码更改为:

      会更有效
      vector<some> v;
      while(/*...*/)
      {
          v.clear();
      
          produce_next( v ); // fill v
          // or something like:
          produce_next( back_inserter(v) );
          // ...
      }
      

      因为在这种情况下,当v.capacity()足够时,可以重新使用已经在vector中分配的内存,而不需要在每次迭代时在produce_next中进行新的分配。

答案 1 :(得分:7)

如果你写这样的lofactory课程:

LargeObject lofactory( ... ) {
    // figure out constructor arguments to build a large object
    return { arg1, arg2, arg3 }  //  return statement with a braced-init-list
}

在这种情况下,没有RVO / NRVO,它是直接构造。该标准的第6.6.3节说“带有 braced-init-list 的A return语句初始化要通过copy-list-initialization从函数返回的对象或引用(8.5。 4)从指定的初始化列表。“

然后,如果您使用

捕获对象
LargeObject&& mylo = lofactory( ... ); 

不会有任何复制,终身将是您所期望的,您可以修改mylo。

所有在任何地方都没有复制,保证。