經過一道面試題來看 C++的RVO 優化

題目二:
   題目我作了下改變,使用了上篇文章(http://my.oschina.net/u/90679/blog/109042)中提到的那個類X,代碼以下:

 1 class X
 2 {
 3 public:
 4     X(){cout<<"default construct"<<endl;}
 5     X(int a):i(a){ cout<<"construct "<<i<<endl;}
 6     ~X(){ cout<<"desconstruct "<<i<<endl;}
 7     X(const X& x):i(x.i)
 8     {
 9         cout<<"copy construct "<<i<<endl;
10     }
11     X& operator++()
12     {
13         cout<<"operator ++(pre) "<<i<<endl;
14         ++i;
15         return *this;
16     }
17     const X operator++(int)
18     {
19         cout<<"operator ++(post) "<<i<<endl;
20         X x(*this);
21         ++i;
22         return x;
23     }
24     X& operator=(int m)
25     {
26         cout<<"operator =(int)"<<endl;
27         i = m;
28         return *this;
29     }
30     X& operator=(const X& x)
31     {
32         cout<<"operator =(X)"<<endl;
33         i=x.i;
34         return *this;
35     }
36     /////////////////////////
37     friend ostream& operator<<(ostream& os,const X& x)
38     {
39         os<<x.i;
40         return os;
41     }
42     friend X operator+(const X& a,const X& b)
43     {
44         cout<<"operator +"<<endl;
45         return X(a.i+b.i);
46     }
47     //////////////////////////
48 public:
49     int i;
50 };

請問如下代碼的輸出是什麼?

1  X a( 10 ),b( 20 );
2  X c = a + b;

咱們來看一下使用GCC4.5(默認編譯選項)以及MSVC9.0(BOTH DEBUG AND RELEASE)編譯後的實際運行結果:
construct 10
construct 20
operator +
construct 30
desconstruct 30
desconstruct 20
desconstruct 10

簡單分析下這個輸出:

construct 10 
construct 20 //對應 X a(10),b(20);
operator +  //調用「+」操做符
construct 30 //調用X(int){...},44行處
desconstruct 30 //變量c 的析構
desconstruct 20 //變量b 的析構
desconstruct 10 //變量a 的析構
 從結果能夠看出,整個執行過程當中沒有輸出「operator=」,說明壓根沒有調用「=」操做符,並且整個過程比我想象的要簡潔高效,沒有臨時對象,沒有拷貝構造。
結果爲何會是這樣呢?這主要歸功於編譯器的返回值優化的能力。
有關返回值優化的知識,限於篇幅我就不仔細介紹了,可是須要特別指出的是MSVC9.0只在RELEASE模式下默認開啓NRVO,即對具名對象的返回值優化,以及返回值優化裏面的一個重要的細節,體如今本例裏就是:爲何中整個輸出中沒有出現"opeartor=",即爲何沒調用"="操做符。

如今咱們將代碼稍微改變一下,改爲下面的樣子:

X a( 10 ),b( 20 ),c;
c
= a + b;  //這裏咱們將c的構造和賦值分開了

執行的結果以下:

construct 10 //構造a
construct 20 //構造b
default construct //構造 c
operator +  //調用「+」操做符
construct 30 //調用X(int){...},44行處
operator =(X) //調用「=」操做符
desconstruct 30 //代碼45行所創建的臨時對象的析構
desconstruct 30 //變量c的析構
desconstruct 20 //變量b的析構
desconstruct 10 //變量c的析構

對比先後的輸出結果,能夠發現多出如下三行
default construct 
operator =(X) 
desconstruct 30  出現這種差別的緣由在於: 定義c的時候會調用默認的構造函數進行初始化,所以第一條語句執行完以後,c已是一個存在的對象,因此第二條語句並無權利去直接修改c的內容,必需要經過調用賦值操做符」=「,所以必需要產生一個臨時對象。而在第一個例子中,由於執行到第二條語句以前c並無被建立,因此編譯器能夠將 表達式a+b的返回值直接構建在c的內存中,從而優化掉臨時對象和對「=」的調用。
相關文章
相關標籤/搜索