我意识到将一个右值赋值给一个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&
实现的。)
const&
以及何时依赖RVO / NRVO时,是否有任何普遍接受的规则或最佳做法?const&
方法比不使用方法更糟糕的情况? (如果LargeObject
实现了那些,那我就想一想关于C ++ 11移动语义的例子......)答案 0 :(得分:13)
让我们考虑最简单的案例:
lofactory( ... ).some_method();
在这种情况下,可以从 lofactory 到调用者上下文的一个副本 - 但可以通过 RVO / NRVO 对其进行优化。
LargeObject mylo2 ( lofactory( ... ) );
在这种情况下,可能的副本是:
const LargeObject& mylo1 = lofactory( ... );
在这种情况下,仍然可以使用一个副本:
引用将绑定到此临时。
所以,
使用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。例如:
Bjarne Stroustrup describes another trick using small mutable flag inside class。他提到的示例代码是here。
然而,即使存在移动语义 - 也存在不能廉价移动的对象。例如,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。
所有在任何地方都没有复制,保证。