16

我知道將一個右值賦給常量左值引用的事實將臨時對象的生命週期擴展到範圍的末尾。然而,我不清楚何時使用這個以及何時依賴返回值優化。對臨時值與返回值優化的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 ? 
} 

根據蘇格蘭人邁爾斯更有效的C++(項目20)的第二方法可以由編譯器進行優化,以構造代替對象(這將是理想的,一個人試圖實現與const&在什麼第一種方法)。

  1. 是否有任何普遍接受的規則或最佳實踐何時使用const&來臨時以及何時依賴RVO/NRVO?
  2. 難道會出現這樣的情況:使用const&方法比不使用方法更糟? (我在想例如約C++ 11移動語義如果LargeObject已經實施的那些...)

回答

12

讓我們考慮最簡單的例子:

lofactory(...).some_method(); 

在這種情況下,一個副本來自到呼叫者上下文的lofactory可能是–,但它可以通過RVO/NRVO進行優化。


LargeObject mylo2 (lofactory(...)); 

在這種情況下可能的副本:

  1. 返回臨時lofactory至呼叫者上下文–可以通過RVO/NRVO
  2. 禁止複製被優化掉構建mylo2 from 臨時 –可以通過複製省音

const LargeObject& mylo1 = lofactory(...); 

被優化掉在這種情況下,存在一個拷貝仍然是可能的:

  1. 返回臨時lofactory來電上下文–可以優化啊y由RVO/NRVO(太!)

引用將綁定到此臨時。


所以,

是否有任何普遍接受的規則或最佳做法時使用常量&來臨時變量,當依靠RVO/NRVO?

正如我上面所說的,即使是在與一個const&情況下,unnecesary複製是可能的,並且它可以通過RVO/NRVO被優化掉。

如果您的編譯器在某些情況下應用了RVO/NVRO,那麼很可能它會在第2階段(上圖)進行復制刪除。因爲在這種情況下,copy-elision比NRVO簡單得多。

但是,在最壞的情況下,您將有一個副本用於const&的情況,當您初始化該值時將有兩個副本。

難道會出現這樣的情況:使用const &方法比不使用它更糟嗎?

我不認爲有這種情況。至少除非你的編譯器使用奇怪的規則來區分const&。 (對於類似情況的例子,我注意到,MSVC沒有爲集合初始化做NVRO。)

(我在想例如約C++ 11移動語義如果LargeObject那些已經實施... )

在C++ 11,如果LargeObject有移動的語義,那麼在最壞的情況下,你會當你初始化值必須爲const&情況下,一個舉動,和兩個動作。所以,const&還是好一點。


所以,一個好的規則將始終綁定臨時變量如果可能的話爲const &,因爲它可能會導致一個副本,如果編譯器不能做一個副本,省音出於某種原因?

不知道應用程序的實際上下文,這似乎是一個很好的規則。

在C++ 11中,可以將臨時值與右值引用綁定 - LargeObject & &。所以,這樣的臨時可以修改。


順便說一下,移動語義仿真可以通過不同的技巧向C++ 98/03提供。例如:

但是,即使存在移動語義 - 有一些對象不能被低價移動。例如,4×4的矩陣類裏面有雙數據[4] [4]。因此,即使在C++ 11中,Copy-elision RVO/NRVO仍然非常重要。順便提一下,Copy-elision/RVO/NRVO發生時 - 比移動更快。


PS,在現實情況下,也有一些需要考慮的其他注意事項:

舉例來說,如果你有函數返回向量,即使移動/ RVO/NRVO /複製,省音會應用 - 它仍然可能不是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重新使用()就足夠了,不需要在每次迭代時在produce_next中進行新的分配。

+0

因此,如果可能的話,_always_將臨時對象綁定到'const&',因爲如果編譯器由於某種原因未能進行復制elision,它可能會阻止拷貝? –

+0

已接受,謝謝! –

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節說:「帶有括號初始化列表的0123.聲明通過copy-list-initialization(8.5.4)從指定的初始化程序列表初始化要從函數返回的對象或引用。

然後,如果你

LargeObject&& mylo = lofactory(...); 

捕捉的對象不會有任何複製,壽命將是你所期望的,你可以修改這款mylo。

而且沒有任何地方複製,保證。

+0

哇,這很有趣!不知道,謝謝! –