NRV優化詳解

2021-07-23 20:32:33 字數 3525 閱讀 4738

大綱:

函式返回區域性物件的拷貝的一般實現方式。

nrv(named return value)優化。

nrv優化觸發的疑問。

一、函式返回區域性物件的拷貝的一般實現方式

比如有這麼一段函式定義:

[cpp]view plain

copy

class

x;  

x bar()    

在學習c++語法時,我們知道了。針對」xbar()」這樣的函式,是返回class x的乙個物件的拷貝。其返回值是乙個物件,比如叫做x2。在執行return時,x2通過呼叫拷貝建構函式,拷貝物件x1來實現其初始化。也就是說,這裡會存在兩個物件x1、x2。那麼這種返回物件的拷貝,是怎麼實現的呢?一般來說,c++編譯器會將上段**中bar的實現轉換成如下的**。

[cpp]view plain

copy

// 函式實現

void

bar(x& __result)   

// 加上乙個額外引數

// 函式呼叫

x x2;                   // 這裡只是預留記憶體,並未呼叫初始化函式

bar(x2);  

通過上述**,我們可見編譯器對於返回物件拷貝的處理方式。

1、函式新增乙個額外引數,為返回物件的引用;

2、函式呼叫前,先申請欲返回物件x2的記憶體空間;

3、將物件x2的引用傳入函式中,並在函式返回前,呼叫x2的拷貝建構函式。

通過上述實現方式

[cpp]view plain

copy

x x2 = bar();  

被轉換成了

[cpp]view plain

copy

x x2;  

bar(x2);  

二、nrv(named return value)優化

上面的實現中,存在著x1、x2兩個物件,而x1的生命週期轉瞬即逝。而且對於bar()的呼叫者來說,根本就沒有x1這個物件,呼叫者想要的只有x2。這樣的實現能不能夠將其優化變得更快呢。編譯器有一種優化方式,直接將x2替代x1。編譯器轉換後的偽**如下。

[cpp]view plain

copy

void

bar(x& __result)    

從**看出,這裡只有乙個物件,也就是傳入的x2。nrv優化後的實現,比原來的實現省去了如下操作:

1)  在堆疊中預留x1的記憶體;

2)  呼叫x的預設建構函式,構造x1

3)  呼叫x的拷貝建構函式,構造x2

4)  呼叫x1的析構函式

5)  堆疊中**x1的記憶體

但是多了乙個操作,就是呼叫x的預設建構函式,構造x2。

對於函式的呼叫者(只關心x2不關心x1)來說,只有乙個區別,就是x2的構造方式由呼叫拷貝建構函式,轉變成了呼叫預設建構函式。

三、nrv優化觸發的疑問

上面的內容在《深度探索c++物件模型》中都有詳細的講解。此外書中還提到了程式設計師必須給class x定義拷貝建構函式才能觸發nrv優化,不然還是按照最初的較慢的方式執行。可是為什麼一定要定義拷貝建構函式才能觸發nrv優化呢。在網上找了半天一直沒有確定的答案。於是我做了如下實驗。有如下**:

[cpp]view plain

copy

class

ctest  

ctest(const

ctest& rctest)  

~ctest()  

private

:  int

a;  

};  

ctest foo()  

intmain()    

在不同的編譯環境的執行結果分別為:

vs2005(debug)

vs2005(release)

g++(-c -o)

也就是說,在vs2005的release環境和g++中,都觸發了編譯器的nrv優化。

然後,再將**中的class ctest的拷貝建構函式去掉,執行結果依次為:

vs2005(debug)

vs2005(release)

g++(-c -o)

我們去掉class ctest的拷貝建構函式後,按照《深度探索》中所說,class ctest的拷貝動作只需要bitwise copy就可以實現。所以編譯器也不會給其合成乙個implicit的拷貝建構函式。也就是說,這個時候class ctest是沒有拷貝構造的。但執行結果和去掉拷貝構造前一樣,vs2005譯

編的release程式和g++中,均使用了nrv優化。

最後將ctest的**改為

[cpp]view plain

copy

class

csub  

csub(const

csub& rcsub)  

};  

class

ctest  

~ctest()  

private

:  int

a;  

csub osub;  

};  

這個時候的執行結果為:

vs2005(debug)

vs2005(release)

g++(-c -o)

此時因為class ctest中有osub,且osub需要呼叫其拷貝建構函式才能完成拷貝,所以編譯器會給其新增乙個implicit的拷貝建構函式。而就算這樣,執行結果依然和前面的一樣。

實驗結果顯示,不管是類有explicit的構造、implicit的拷貝構造還是沒有拷貝構造,在vs2005的release和g++下都會觸發nrv優化,在vs2005(debug)下都沒有nrv優化。所以可以得出結論,在這兩個編譯器中,nrv優化和拷貝建構函式是否定義沒關係。

那麼為什麼《深度探索》的作者會說有關係呢。網上找到了一種說法,我覺得比較有道理(傳送門)。擷取其中的話就是「早期的 cfront需要乙個開關來決定是否應該對**實行nrv優化,這就是是否有客戶(程式設計師)顯式提供的拷貝構造

函式:如 果客戶沒有顯示提供拷貝建構函式,那麼cfront認為客戶對預設的逐位拷貝語義很滿意,由於逐位拷貝本身就是很高效的,沒必要再對其實施nrv優化;但 如果客戶顯式提供了拷貝建構函式,這說明客戶由於某些原因(例如需要深拷貝等)擺脫了高效的逐位拷貝語義,其拷貝動作開銷將增大,所以將應對其實施nrv 優化,其結果就是去掉並不必要的拷貝函式呼叫。

」我認為,因為作者在書中一直都是以cfront來舉例說明的,所以其才會有nrv開關的說法。

其實不管cfront如何,現在已經確定的是vs2005(release)和g++都會執行nrv優化。而nrv優化會導致原本預想中的呼叫拷貝建構函式變成呼叫別的建構函式(視函式中的物件呼叫的建構函式而定)。這一點一定要注意,因為一旦這個時候,拷貝建構函式和別的建構函式提供的功能不同(其實一直都不應該這樣),會導致debug和release出現執行結果不同的情況。

自己動手理解NRV優化

自己動手理解nrv優化 2010.6.29 燭秋2010.8.20整理 說明 本文整理自 一 nrv的簡單理解 nrv是named return value的簡稱。nrv優化簡單的說 有一條語句,a a f 其中f 是乙個函式,函式裡邊申請了乙個a的物件b,然後把物件b返回。在物件返回的時候,一般情...

NRV優化所帶來的困惑

我們知道要了解編譯器在做什麼,nrv優化應該是乙個無法避免的問題,下面來看乙個例子 include iostream 從這兩個程式的執行來看nrv優化好像並不是那麼如你想象中的好 using namespace std include class text private double arry 1...

ListView優化詳解

一 為什麼需要優化?二 listview優化的核心 view的復用 getview方法中有三個引數position標識listview正繪製第幾個item。convertview相當於view控制項的快取裝置,通過layoutinflater可將item的布局檔案壓縮成乙個檢視賦值給convertv...