題目二:
題目我做了下改變,使用了上篇文章中提到的那個類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的內存中,從而優化掉臨時對象和對“=”的調用。