C++以對象作爲返回值時編譯器的優化,以及臨時變量的析構時機

印象中,函數調用的時候,參數past by value、返回值return by value,總是伴隨着對象的複製。


實際上參數傳遞是這樣的,但是返回值有可能不是這樣的(雖然大部分都是面臨拷貝構造函數的調用),這取決於編譯器。

#include<string>  
#include<list>  
#include<iostream>  

using namespace std;  
  
class C  
{  
   public:
   	    C()
   	    {
   	   	    cout<<"C default constructor(),this="<<this<<endl; 
   	    }  
            
        C(const C &c)  
        {  
            cout<<"C const copy constructor(),this="<<this<<",reference="<<&c<<endl;                                  
        }  
          
        C(C &c)  
        {  
            cout<<"C nonconst copy constructor(),this="<<this<<",reference="<<&c<<endl;            
        }  
          
        const C & operator=(const C &c)  
        {  
            cout<<"C assignment(),this="<<this<<endl;  
            return *this;  
        }  
        ~C()  
        {  
            cout<<"C destructor(),this="<<this<<endl;  
        } 
};  
      
      
C test_1(int i)  
{  
	 cout<<"entering test_1"<<endl; 
   C x;  
   C a;           //a會析構
   cout<<"leaving test_1"<<endl; 
   return x;      //return之後棧不清空,x不會析構,即使編譯器已經將優化設置成-O0  
  
}  
      
C test_2(int i) 
{  
	 cout<<"entering test_2"<<endl; 
   C x;  
   C a;  
   
   cout<<"leaving test_2"<<endl;
   if(i>0) 
       return x;   
   else  
       return a;    //x和a都會析構,返回的時候,先調用拷貝構造函數,初始化返回值(此處爲main裏面的z),  
                    //然後再析構a和x  
} 


C test_3(C t)
{
	return t;        //此處導致t的構造和析構
} 

C test_4(C t)
{
	C x=t;
	return x;        //此處導致t的構造和析構,但是x只會構造不會析構
} 

int main()
{
   cout<<"invoking test_1"<<endl;
   C y=test_1(1); //這種調用不會有拷貝構造函數,y直接爲test_1函數棧裏面生成的對象,編譯器優化的結果
   cout<<"end invoke test_1"<<endl;
   
   cout<<"================華麗分割線================="<<endl;
   
   cout<<"invoking test_2"<<endl;
   C z=test_2(1);  //這種情況會調用拷貝構造函數(nonconst版本),初始化z
   cout<<"end invoke test_2"<<endl; 
   
   cout<<"================華麗分割線================="<<endl;
   
   cout<<"invoking test_3"<<endl;
   C a=test_3(y);  
   cout<<"end invoke test_3"<<endl; 
   
   
   cout<<"================華麗分割線================="<<endl;
   
   cout<<"invoking test_4"<<endl;
   C b=test_4(y);  
   cout<<"end invoke test_4"<<endl;  
   
  cout<<"================華麗分割線================="<<endl;
   
   cout<<"開始測試臨時變量何時析構"<<endl;
   test_2(1),                         //(注意結束處是逗號)此處返回的C沒有指定任何變量,編譯器會生成臨時變量                                         
   cout<<"結束測試臨時變量何時析構"<<endl;//臨時變量會再語句的第一個分號處析構,cout完成之後析構
   
   cout<<"================華麗分割線================="<<endl;
   
   cout<<"開始測試臨時變量何時析構"<<endl;
   test_2(1);                         //(注意結束處是分號)此處返回的C沒有指定任何變量,編譯器會生成臨時變量                                         
   cout<<"結束測試臨時變量何時析構"<<endl;//臨時變量會再語句的第一個分號處析構,cout開始之前析構
   
   cout<<"================華麗分割線================="<<endl;
   cout<<"================下面開始析構棧裏面的變量了,啦啦啦================="<<endl;
   cout<<"================析構順序按照入棧的順序,後進先出,後構造,先析構==========="<<endl;
   return 0;

}



運行結果:


AlexdeMacBook-Pro:~ alex$ a.out
invoking test_1
entering test_1
C default constructor(),this=0x7fff5929baa8
C default constructor(),this=0x7fff5929b8d8
leaving test_1
C destructor(),this=0x7fff5929b8d8
end invoke test_1
================華麗分割線=================
invoking test_2
entering test_2
C default constructor(),this=0x7fff5929b8d8
C default constructor(),this=0x7fff5929b8d0
leaving test_2
C nonconst copy constructor(),this=0x7fff5929ba98,reference=0x7fff5929b8d8
C destructor(),this=0x7fff5929b8d0
C destructor(),this=0x7fff5929b8d8
end invoke test_2
================華麗分割線=================
invoking test_3
C nonconst copy constructor(),this=0x7fff5929ba88,reference=0x7fff5929baa8
C nonconst copy constructor(),this=0x7fff5929ba90,reference=0x7fff5929ba88
C destructor(),this=0x7fff5929ba88
end invoke test_3
================華麗分割線=================
invoking test_4
C nonconst copy constructor(),this=0x7fff5929ba78,reference=0x7fff5929baa8
C nonconst copy constructor(),this=0x7fff5929ba80,reference=0x7fff5929ba78
C destructor(),this=0x7fff5929ba78
end invoke test_4
================華麗分割線=================
開始測試臨時變量何時析構
entering test_2
C default constructor(),this=0x7fff5929b8d8
C default constructor(),this=0x7fff5929b8d0
leaving test_2
C nonconst copy constructor(),this=0x7fff5929ba70,reference=0x7fff5929b8d8
C destructor(),this=0x7fff5929b8d0
C destructor(),this=0x7fff5929b8d8
結束測試臨時變量何時析構
C destructor(),this=0x7fff5929ba70
================華麗分割線=================
開始測試臨時變量何時析構
entering test_2
C default constructor(),this=0x7fff5929b8d8
C default constructor(),this=0x7fff5929b8d0
leaving test_2
C nonconst copy constructor(),this=0x7fff5929ba68,reference=0x7fff5929b8d8
C destructor(),this=0x7fff5929b8d0
C destructor(),this=0x7fff5929b8d8
C destructor(),this=0x7fff5929ba68
結束測試臨時變量何時析構
================華麗分割線=================
================下面開始析構棧裏面的變量了,啦啦啦=================
================析構順序按照入棧的順序,後進先出,後構造,先析構===========
C destructor(),this=0x7fff5929ba80
C destructor(),this=0x7fff5929ba90
C destructor(),this=0x7fff5929ba98
C destructor(),this=0x7fff5929baa8
AlexdeMacBook-Pro:~ alex$
AlexdeMacBook-Pro:~ alex$




結論:

一:return by value時候編譯器的優化

編譯器在能夠做優化的時候,會盡量幫你做優化,比如test_1,總是將棧裏面的x直接給調用者,避免了多一次的析構和構造。即使在關閉編譯器優化的時候,它依然給你做了這個動作。但是在test_2裏面,返回值是動態的,隨着參數變動而變動,編譯器是沒有辦法得知保留哪個的,於是索性都析構了。


在Effective C++ Item 21,page 94, Don't try to return a reference when you must return an object.

作者說:C++和所有編程語言一樣,允許編譯器實現者施行最優化,用以改善產出碼的效率卻不改變其可觀察的行爲。


g++確實對此做了優化,但是動態返回值,編譯器卻無能爲力。你無法要求編譯器按照每個分支,生成不同的代碼。否則在複雜的程序下,生成的可執行文件大小那將無法估計了


二:臨時變了的析構時機

臨時變量,總是在執行完生成臨時變量的那一行代碼之後析構

(是不是比較拗口?)

那就這樣說吧:生成臨時變量之後,遇到第一個分號,析構函數開始調用






發佈了18 篇原創文章 · 獲贊 25 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章