Rob*_*ger 20 c++ optimization const-reference temporary-objects return-value-optimization
我知道为一个const左值引用分配一个rvalue会延长临时值的生命周期,直到范围结束.但是,我不清楚何时使用它以及何时依赖返回值优化.
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 ?
}
Run Code Online (Sandbox Code Playgroud)
根据Scot Meyers的"更有效的C++"(第20项),编译器可以优化第二种方法来构建适当的对象(这将是理想的,并且正是人们试图用const&
第一种方法实现的).
const&
临时工具以及何时依赖RVO/NRVO?const&
方法比不使用该方法更糟糕的情况?(我正在考虑关于C++ 11移动语义的例子,如果LargeObject
实现了那些......)Evg*_*yuk 14
让我们考虑最简单的情况:
lofactory( ... ).some_method();
Run Code Online (Sandbox Code Playgroud)
在这种情况下,从lofactory到调用者上下文的一个副本是可能的 - 但它可以通过RVO/NRVO进行优化.
LargeObject mylo2 ( lofactory( ... ) );
Run Code Online (Sandbox Code Playgroud)
在这种情况下,可能的副本是:
const LargeObject& mylo1 = lofactory( ... );
Run Code Online (Sandbox Code Playgroud)
在这种情况下,仍然可以使用一个副本:
引用将绑定到此临时.
所以,
是否有任何普遍接受的规则或最佳实践何时使用const和临时工,何时依赖RVO/NRVO?
如上所述,即使在一个案例中const&
,也可以进行不必要的复制,并且可以通过RVO/NRVO进行优化.
如果您的编译器在某些情况下应用RVO/NVRO,那么很可能它会在第2阶段(上面)进行复制省略.因为在这种情况下,复制省略比NRVO简单得多.
但是,在最坏的情况下,您将拥有该const&
案例的一个副本,并在您初始化该值时使用两个副本.
是否存在使用const和方法比不使用它更糟糕的情况?
我不认为有这种情况.至少除非你的编译器使用区别的奇怪规则const&
.(有关类似情况的示例,我注意到MSVC不会对集合初始化执行NVRO.)
(我正在考虑关于C++ 11移动语义的例子,如果LargeObject实现了那些......)
在C++ 11中,如果LargeObject
有移动语义,那么在最坏的情况下,你将有一个移动的const&
情况,并在你初始化值时移动两个.所以,const&
还是好一点.
因此,如果编译器由于某种原因无法执行复制省略,那么一个好的规则就是始终将临时值绑定到const&,如果可能的话,它可能会阻止复制?
在不知道实际应用背景的情况下,这似乎是一个很好的规则.
在C++ 11中,可以将临时绑定到右值引用 - LargeObject &&.所以,这样的临时性可以修改.
顺便说一句,移动语义仿真可以通过不同的技巧提供给C++ 98/03.例如:
Bjarne Stroustrup 描述了在类中使用小型可变标志的另一个技巧.他提到的示例代码就在这里.
然而,即使存在移动语义 - 也存在不能廉价移动的对象.例如,4x4矩阵类内部有双数据[4] [4].因此,即使在C++ 11中,Copy-elision RVO/NRVO仍然非常重要.顺便说一句,当Copy-elision/RVO/NRVO发生时 - 它比移动更快.
PS,在实际情况下,还应考虑一些额外的事情:
例如,如果你有返回向量的函数,即使应用Move/RVO/NRVO/Copy-Elision - 它仍然可能不是100%有效.例如,考虑以下情况:
while(/*...*/)
{
vector<some> v = produce_next(/* ... */); // Move/RVO/NRVO are applied
// ...
}
Run Code Online (Sandbox Code Playgroud)
将代码更改为:
vector<some> v;
while(/*...*/)
{
v.clear();
produce_next( v ); // fill v
// or something like:
produce_next( back_inserter(v) );
// ...
}
Run Code Online (Sandbox Code Playgroud)
因为在这种情况下,当v.capacity()足够时,可以重用已经分配的向量内存,而不需要在每次迭代时在produce_next中进行新的分配.
如果你这样写你的lofactory
课:
LargeObject lofactory( ... ) {
// figure out constructor arguments to build a large object
return { arg1, arg2, arg3 } // return statement with a braced-init-list
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,没有RVO/NRVO,它是直接构造.该标准的6.6.3节说" return
带有braced-init-list的语句初始化了从指定的初始化列表中通过copy-list-initialization(8.5.4)从函数返回的对象或引用."
然后,如果你捕获你的对象
LargeObject&& mylo = lofactory( ... );
Run Code Online (Sandbox Code Playgroud)
没有任何复制,终身将是你所期望的,你可以修改mylo.
所有在任何地方都没有复制,保证.