让我们考虑最简单的情况:
lofactory( ... ).some_method();
在这种情况下,从lofactory到调用者上下文的副本是可能的——但它可以通过RVO/NRVO优化掉。
LargeObject mylo2 ( lofactory( ... ) );
在这种情况下,可能的副本是:
- 从lofactory临时返回到调用者上下文 - 可以通过RVO/NRVO优化掉
- 从临时复制构建mylo2 - 可以通过复制省略进行优化
const LargeObject& mylo1 = lofactory( ... );
在这种情况下,仍然可以有一个副本:
- 从lofactory临时返回到调用者上下文 - 可以通过RVO/NRVO优化掉(也是!)
引用将绑定到这个临时。
所以,
是否有任何普遍接受的规则或最佳实践何时使用 const& 来临时以及何时依赖 RVO/NRVO?
正如我上面所说,即使在 的情况下const&
,也可能会出现不必要的副本,并且可以通过RVO/NRVO对其进行优化。
如果您的编译器在某些情况下应用了RVO/NVRO,那么它很可能会在第 2 阶段(上图)执行复制省略。因为在这种情况下,复制省略比 NRVO 简单得多。
但是,在最坏的情况下,您将拥有一份该const&
案例的副本,并在您初始化值时拥有两份副本。
会不会出现使用 const& 方法比不使用更糟糕的情况?
我不认为有这样的情况。至少除非你的编译器使用奇怪的规则来区分const&
. (对于类似情况的一个示例,我注意到 MSVC 不会为聚合初始化执行 NVRO。)
(例如,如果 LargeObject 实现了那些,我正在考虑 C++11 移动语义......)
在 C++11 中,如果LargeObject
具有移动语义,那么在最坏的情况下,您将针对该const&
情况进行一次移动,并且在初始化值时进行两次移动。所以,const&
还是好一点的。
因此,一个好的规则是始终将临时对象绑定到 const& 如果可能,因为如果编译器由于某种原因未能执行复制省略,它可能会阻止复制?
在不知道应用程序的实际上下文的情况下,这似乎是一个很好的规则。
在 C++11 中,可以将临时绑定到右值引用 - LargeObject&&。所以,这样的临时可以修改。
顺便说一句,移动语义仿真可通过不同的技巧在 C++98/03 中使用。例如:
然而,即使存在移动语义 - 也有无法廉价移动的对象。例如,内部带有双数据[4][4] 的 4x4 矩阵类。因此,即使在 C++11 中,复制省略 RVO/NRVO 仍然非常重要。顺便说一句,当复制省略/RVO/NRVO 发生时 - 它比移动更快。
PS,在实际情况下,还有一些额外的事情需要考虑:
例如,如果您有返回向量的函数,即使将应用 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 中已经分配的内存,而无需在每次迭代时在 producer_next 中进行新的分配。