rvo (return value optimization) 和nrvo (named return value optimization) 是c++在處理 「返回乙個class object的函式」 時常用的優化技術,主要作用就是消除臨時物件的構造和析構成本。目前我正在研究《深度探索c++物件模型》,對於這兩種常見的編譯器優化有了初步的了解。接下來以乙個名叫point3d的類和乙個factory函式為例來總結一下。
point3d類定義如下:
[cpp]view plain
copy
class point3d
point3d(const point3d &other): x(other.x), y(other.y), z(other.z)
~point3d()
point3d& operator=(const point3d &rhs)
cout << "operator = "
<< endl;
return *this;
} int x;
int y;
int z;
};
我們定義了三個成員變數x, y, z,為了簡單起見,我們將它們都定義為public訪問屬性。接下來,我們定義了該類的預設建構函式、拷貝建構函式、賦值運算子和析構函式,它們都會通過列印一些字元來追蹤函式呼叫。為了簡單起見,我們不定義移動建構函式和移動賦值運算子。
接下來我們定義乙個函式factory,它產生並返回乙個point3d物件:
[cpp]view plain
copy
point3d factory()
整體**如下:
[cpp]view plain
copy
#include
using
namespace std;
class point3d
point3d(const point3d &other): x(other.x), y(other.y), z(other.z)
~point3d()
point3d& operator=(const point3d &rhs)
cout << "operator = "
<< endl;
return *this;
} int x;
int y;
int z;
};
point3d factory();
int main()
point3d factory()
接下來我們討論「不做任何返回值優化」、「只做rvo不做nrvo」和「不光做rvo也做nrvo」三種情況
(0)不做任何返回值優化
gcc中有乙個-fno-elide-constructors的命令,可以去掉任何返回值優化。我們在編譯時加上這個命令,觀察到程式的輸出如下。我們加上注釋來說明程式執行過程
constructor 1 2 3 //構造出factory()中的區域性物件po
copy constructor //呼叫point3d的拷貝建構函式,用po構造出乙個臨時物件,姑且稱之為臨時物件_temp
destructor //析構factory()中的區域性物件po
copy constructor //呼叫point3d的拷貝建構函式,用臨時物件_temp構造出main()函式中的區域性物件p
destructor //析構臨時物件_temp
1 2 3 //輸出x, y, z
destructor //析構main()函式中的區域性物件p
可以看到,不做任何返回值優化時我們會承擔兩次拷貝建構函式和兩次析構函式呼叫的成本。
(1)使用rvo而不用nrvo
vs在debug模式下使用rvo而不用nrvo。我們可以將原來的**在vs中進行測試,觀察到程式的輸出如下:
constructor 1 2 3 //構造出factory()中的區域性物件po
copy constructor
destructor
1 2 3 //輸出x, y, z
destructor //析構main()函式中的區域性物件p
我們可以發現,使用了rvo之後我們減少了一次拷貝建構函式和一次析構函式的呼叫。
事實上,rvo的原理是,將「返回乙個類物件的函式」的返回值當做該函式的引數處理。具體而言,在上面的例子中,factory()函式會被改寫成如下的形式:
[cpp]view plain
copy
//c++偽**
void factory(point3d &_result)
而對應的函式呼叫則會被改寫成如下的形式:
[cpp]view plain
copy
point3d p; //不做初始化
factory(p); //將p變為函式引數,即呼叫函式factory()來初始化p
這樣我們就可以很清楚地發現,上文的輸出結果裡拷貝建構函式的呼叫是為了從factory()中的區域性物件po構造出函式引數_result,而析構函式的呼叫則是為了析構po。換句話說,進行了rvo之後,我們的factory()函式只使用了乙個叫做po的區域性物件,接下來該函式利用這個po物件直接構造出了factory()函式外面,main()函式裡面的物件p。
這樣做當然比不使用任何返回值優化要好,因為它減少了一次拷貝建構函式的呼叫和一次析構函式的呼叫。然而我們偉大的工程師們依然不知足。能不能把這個區域性物件po也給省略掉呢?換句話說,能不能讓factory()函式直接構造出物件p呢?如果能的話,我們就可以再次減少一次拷貝建構函式(用來利用po構造出_result)和一次析構函式(用來析構po)的呼叫。
(2)在只使用rvo不使用nrvo時再次優化拷貝建構函式和析構函式的呼叫
我們可以將factory()函式改寫成如下的形式:
[cpp]view plain
copy
point3d factory()
執行程式,得到的輸出如下:
constructor 1 2 3
1 2 3
destructor
我們可以發現,和上面(1)中的輸出相比,減少了一次拷貝建構函式和析構函式的呼叫。為什麼呢?
實際上,在這時,factory()函式被改寫成如下形式:
[cpp]view plain
copy
//c++偽**
void factory(point3d &_result)
main()中的factory()函式呼叫依然保持(1)中的形式不變
[cpp]view plain
copy
point3d p; //不做初始化
factory(p); //將p變為函式引數,即呼叫函式factory()來初始化p
這時,由於我們的factory函式返回的是乙個匿名的point3d物件,編譯器就可以進行更進一步的優化,省略掉factory()中的區域性物件的構造、析構和從這個區域性物件到函式引數的拷貝,從而減少一次拷貝建構函式和一次析構函式的呼叫。這時,我們可以認為,factory()函式直接構造出了main()函式中的p物件。
(3)既使用rvo又使用nrvo
(2)的不足之處在於程式設計師必須通過手動返回臨時物件來優化**。nrvo使得在程式設計師寫出和(1)中相同**的情況下也能起到(2)中的效果,即,讓factory()函式直接構造出main()函式中的p物件。
這時,factory()函式的寫法依然和(1)中而不是(2)中相同。
[cpp]view plain
copy
point3d factory()
編譯器會用_result直接替換po,也就是改寫成如下**:
[cpp]view plain
copy
//c++偽**
void factory(point3d &_result)
當然,如果程式設計師寫出的是這樣的**:
[cpp]view plain
copy
point3d factory()
則會被改寫為:
[cpp]view plain
copy
//c++偽**
void factory(point3d &_result)
(如果只使用rvo則會被改寫成如下**)
[cpp]view plain
copy
//c++偽**
void factory(point3d &_result)
可以很明顯地看出來:1、在rvo機制上加上nrvo機制的直接表現就是編譯器直接用「用來替代函式返回值的引數」取代「該函式返回的那個區域性物件」,在這裡,表現為編譯器直接使用_result取代了po物件。2、節約了一次拷貝建構函式和一次析構函式呼叫的成本。因為「該函式返回的那個區域性物件」被「用來代替函式返回值的那個引數」所取代,所以我們無需構造出該區域性物件,自然也無需析構它。
目前的常用c++編譯器都支援nrvo,c++11也已經把「允許編譯器進行nrvo」寫入了標準。經過測試,gcc編譯器在debug和release模式下均支援nrvo,vs在debug模式下不支援nrvo,僅支援rvo,而在release模式下也支援nrvo。
C 中的RVO優化和NRVO優化
rvo return value optimization 和nrvo named return value optimization 是c 在處理乙個函式返回類物件並將返回值賦給另乙個物件時,為了減少拷貝構造次數以及析構次數而採用的一種編譯器優化技術。本篇部落格以 深度探索c 物件模型 中例子來總...
C 中的RVO和NRVO優化
rvo return value optimization 和nrvo named return value optimization 是c 在處理乙個函式返回類物件並將返回值賦給另乙個物件時,為了減少拷貝構造次數以及析構次數而採用的一種編譯器優化技術。本篇部落格以 深度探索c 物件模型 中例子來總...
編譯器優化RVO和NRVO
編譯器選擇 如果想自己實踐rvo和nrvo,不要在vs下嘗試 這裡的vs不是指vs code 用支援g 的編譯器去測試 我這裡用的是mingw 因為vs無論在debug還是release下都是無法關閉rvo優化的,dubug下可以關閉nrvo優化。而g 預設都是執行nrv0優化的。rvo nrvo ...