大家可能對這個臨時對象這個概念還不是很清楚,那么首先我們花一些時間來理解臨時對象。首先看下面一端代碼:
1 #include <iostream> 2 void swap( int &a,int &b) 3 { 4 int temp; 5 temp=a; 6 a=b; 7 b=temp; 8 } 9 10 int main(int argc,char** argv) 11 { 12 int a=1,b=2; 13 swap(a,b); 14 std::cout<<a<<"-----"<<b<<std::endl; 15 return 0; 16 } 17 結果為 18 2-----1
可能大多數園友,認為"int temp"是"臨時對象",但是其實不然,"int temp"僅僅是swap函數的局部變量。
臨時對象是代碼中看不到的,但是實際程序中確實存在的對象。臨時對象是可以被編譯器感知的。
為什么研究臨時對象?
主要是為了提高程序的性能以及效率,因為臨時對象的構造與析構對系統開銷也是不小的,所以我們應該去了解它們,知道它們如何造成,從而盡可能去避免它們。
臨時對象建立一個沒有命名的非堆對象會產生臨時對象。(不了解什么是堆對象和非堆對象,可以參考C++你最好不要做的這一博文,這里面有介紹。)這種未命名的對象通常在三種條件下產生:為了使函數成功調用而進行隱式類型轉換時候、傳遞函數參數和函數返回對象時候。
那么首先看看為了使函數成功調用而進行隱式類型轉換。
1 #include <iostream> 2 int countChar(const std::string & s,const char c) 3 { 4 int count=0; 5 for(int i=0;i<s.length( );i++) 6 { 7 if(*(s.c_str( )+i) == c) 8 { 9 count++; 10 } 11 } 12 return count; 13 } 14 15 int main(int argc,char** argv) 16 { 17 char buffer[200]; 18 char c; 19 std::cout<<"please input the string:"; 20 std::cin>>buffer; 21 std::cout<<"please input the char which you want to chount:"; 22 std::cin>>c; 23 int count=countChar(buffer,c); 24 std::count<<"the count is:"<<count<<std::endl; 25 return 0; 26 }
結果為:
這里調用函數countChar(const std::string& s,const char& c),那么我們看看這個函數的形參是const std::string &s,形參類型為const std::string,但是實際上傳遞的是char buffer[200]這個數組。其實這里編譯器為了使函數調用成功做了類型轉換,char *類型轉換為了std::string類型,這個轉換是通過一個賦值構造函數進行的,以buffer做為參數構建一個std::string類型的臨時對象。當constChar返回時,即函數撤銷,那么這個std::string臨時對象也就釋放了。但是其實從整個程序上來說臨時對象的構造與釋放是不必要的開銷,我們可以提高代碼的效率修改一下代碼避免無所謂的轉換。所以知道臨時對象的來源,可以對程序性能上有一個小小提升。
注意僅當通過傳值方式傳遞對象或者傳遞常量引用參數,才會發生這類型的轉換,當傳遞非常量引用的參數對象就不會發生。因為傳遞非常量的引用參數的意圖就是想通過函數來改變其傳遞參數的值,但是函數其實是改變的類型轉換建立的臨時對象,所以意圖無法實現,編譯器干脆就直接拒絕。
第二種情況是大家熟悉的函數傳遞參數的時候,會構造對應的臨時對象。看下面一段代碼運行的結果想必就一清二楚了。
1 #include<iostream> 2 class People 3 { 4 public: 5 People(std::string n,int a) 6 :name(n),age(a) 7 { 8 std::count<<"h2"<<std::endl; 9 } 10 People( ) 11 { 12 std::count<<"h1"<<std::endl; 13 } 14 People(const People& P) 15 { 16 name=p.name; 17 age=p.age; 18 std::cout<<"h3"<<std::endl; 19 } 20 std::string name; 21 int age; 22 }; 23 24 void swap(People p1,People p2) 25 { 26 People temp; 27 temp.age=p1.age; 28 temp.name=p1.name; 29 p1.age=p2.age; 30 p1.name=p2.name; 31 p2.age=temp.age; 32 p2.name=temp.name; 33 } 34 35 int main(int argc, char ** argv) 36 { 37 People p1("tom",18),p2("sam",19); 38 swap(p1,p2); 39 return 0; 40 }
結果為:
這里分析下前面兩個"h2"是通過調用構造函數People(std::string n,int a)打印出來的,而"h3"就是通過調用復制構造函數People(const People&)而建立臨時對象打印出來的,h1是調用默認構造函數People( )打印出來的。那么怎么避免臨時對象的建立呢?很簡單,我們通過引用實參而達到目的
1 void swap(People &p1,People &p2)
第三種情景就是函數返回對象時候。這里要注意臨時對象的創建是通過復制構造函數構造出來的。
例如 const Rationanl operator+(Rationanl a,Rationanl b)該函數的返回值的臨時的,因為它沒有被命名,它只是函數的返回值。每回必須為調用add構造和釋放這個對象而付出代價。
1 #include <iostream> 2 class Rationanl 3 { 4 public: 5 Rationanl(int e,int d) 6 :_elemem(e),_denom(d) 7 { 8 std::cout<<"h2"<<std::endl; 9 } 10 void show( ) const; 11 int elemem() const {return _elemem;} 12 int denom() const {return _denom;} 13 void setElemon(int e){_elemon=e;} 14 void setDenom(int d) {_denom=d;} 15 Rationanl(const Rationanl &r); 16 Rationanl & operator=(const Rationanl &r); 17 private: 18 int _elemem; 19 int _denom; 20 }; 21 Rationanl::Rationanl(const Rationanl &r) 22 { 23 setElemon(r.elemon( )); 24 setDenom(r.denom( ) ); 25 std::cout<<"h3"<<std::endl; 26 } 27 Rationanl & Rationanl::operator=(const Rationanl &r) 28 { 29 setElemon(r.elemon( )); 30 setDenom(r.denom( ) ); 31 std::cout<<"h4"<<std::endl; 32 return *this; 33 } 34 35 void Rationanl::show( ) 36 { 37 std::cout<<_elemen<<"/"<<_denom<<std::endl; 38 } 39 const Rationanl operator*(const Rationanl lhs,const Rationanl rhs) 40 { 41 return Rational result(lhs.elemen*rhs.elemen,rhs.denom*rhs.denom); 42 } 43 44 int main(int argc,char **argv) 45 { 46 Rationanl r1(1,2),r2(1,3) 47 Rationanl r3=r1*r2; //GCC做了優化,沒有看到臨時變量。編譯器直接跳過建立r3,使用賦值符號 48 r3.show( ); 49 //相當於 (r1*r2).show( ); 50 return 0; 51 }
結果為:
這里很可惜沒有看到我們想到看到的結果,結果應該為h2,h2,h2,h3,h4,應該是在返回值的時候有一個賦值構造函數,建立臨時變量的,后來經筆者網上查找資料證實GCC做了優化。