臨時對象全解

二、通常定義變量和對象時要用標識符命名,稱命名對象,而動態的稱無名對象(請注意與棧區中的臨時對象的區別,二者徹底不一樣:生命期不一樣,操做方法不一樣,臨時變量對程序員是透明的)。
4.動態分配的變量或對象的生命期。無名變量的生命期並不依賴於創建它的做用域,好比在函數中創建的動態對象在函數返回後仍可以使用。咱們也稱堆空間爲自由空間(free store)就是這個緣由。但必須記住釋放該對象所佔堆空間,並只能釋放一次,在函數內創建,而在函數外釋放是一件很容易失控的事,每每會出錯,因此永遠不要在函數體內申請空間,讓調用者釋放,這是一個不好的作法。你再怎麼當心翼翼也可能會帶來錯誤。
類在堆中申請內存 :

http://boke.25k5.com/kan279425.html


19. 瞭解臨時對象的來源

臨時對象:沒有命名的non-heap object。 一般發生於兩種狀況:

隱式類型轉換:在參數爲by value或reference-to-const傳遞時會發生(在reference-to-non-const時不會發生,由於若是能夠的話,會容許臨時對象被修改)
函數返回對象:經過RVO優化
20. 協助完成「返回值優化(RVO)」

有些函數必定要by-value返回(好比operator*),所以可使用constructor arguments取代返回的對象,完成retrun value optimization(RVO)。

const Rational operator*(const Rational* lhs, const Rational& rhs) {
    return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
}

Rational a = 10;
Rational b(1, 2);
Rational c = a * b;


C++的臨時變量是編譯器在須要的時候自動生成的臨時性變量,它們並不在代碼中出現.可是它們在編譯器生成的二進制編碼中是存在的,
 也建立和銷燬.在C++語言中,臨時變量的問題格外的重要,由於每一個用戶自定義類型的臨時變量都要出發用戶自定義的構造函數和析構函數(若是用戶提供了)
 
 又是該死的編譯器!又該有人抱怨編譯器總在本身背後幹着偷偷摸摸的事情了.可是若是離開了編譯器的這些工做,咱們可能步履維艱.
 
 若是X是一個用戶自定義的類型,有默認構造函數,拷貝構造函數,賦值運算函數,析構函數(這也是類的4個基本函數),那麼請考慮如下代碼:
 X get(X arg)
 {
  return arg;
 }
 
 X a;
X b = get(a);


 讓咱們分析一下代碼執行過程當中發生了什麼?
 首先我要告訴你一個祕密:對於一個函數來講,不管是傳入一個對象仍是傳出一個對象其實都是不可能的.
 讓一個函數傳入或傳出一個內置的數據類型,例如int,是很容易的,可是對於用戶自定義類型得對象卻很是的困難,由於編譯器總得找地方爲這些對象
 寫上構造函數和析構函數,不是在函數內,就是在函數外,除非你用指針或引用跳過這些困難
 
 那麼怎麼辦?在這裏,編譯器必須玩一些必要的小花招,嗯,其中的關鍵偏偏就是臨時變量
 
 對於以對象爲形參的函數:
  void foo(X x0)
 {
 }

 X xx;
 foo(xx);
 編譯器通常按照如下兩種轉換方式中的一種進行轉換
 1.在函外提供臨時變量
 void foo(X& x0)   //修改foo的聲明爲引用
 {
 }
 X xx;        //聲明xx
 X::X(xx);      //調用xx的默認構造函數
 X __temp0;     //聲明臨時變量__temp0
 X::X(__temp0, xx); //調用__temp0的拷貝構造函數
 foo(__temp0);    //調用foo
 X::~X(__temp0);   //調用__temp0的析構函數
 X::~X(xx);     //調用xx的析構函數

 2.在函數內提供臨時變量
  void foo(X& x0)   //修改foo的聲明爲引用
 {
  X __temp0;     //聲明臨時變量__temp0
  X::X(__temp0, x0); //調用__temp0的拷貝構造函數
  X::~X(__temp0);   //調用__temp0的析構函數
 }
 X xx;      //聲明xx
 X::X(xx);      //調用xx的默認構造函數
 foo(xx);      //調用foo
 X::~X(xx);     //調用xx的析構函數
 
 不管是在函數的內部聲明臨時變量仍是在函數的外部聲明臨時變量,其實都是差很少的,這裏的含義是說既然參數要以傳值的
 語意傳入函數,也就是實參xx其實並不能修改,那麼咱們就用一個一摸同樣臨時變量來移花接木,完成這個傳值的語意
 可是這樣作也不是沒有代價,編譯器要修改函數的聲明,把對象改成對象的引用,同時修改全部函數調用的地方,代價確實巨大啊,
 可是這只是編譯器不高興而已,程序員和程序執行效率卻沒有影響


 對於以對象爲返回值的函數:
  X foo()
 {
  X xx;
  return xx;
 }
 
 X y = foo();

 編譯器通常按照如下方式進行轉換
  void foo(X& __temp0) //修改foo的聲明爲引用
 {
  X xx;        //聲明xx
  X::X(xx);      //調用xx的默認構造函數

  __temp0::X::X(xx); //調用__temp0的拷貝構造函數
  X::~X(xx);     //調用xx的析構函數
 }
 
 X yy;         //聲明yy
 X __temp0;      //聲明臨時變量__temp0
 foo(__temp0);     //調用foo
 X::X(yy, __temp0);  //調用yy的拷貝構造函數
 X::~X(__temp0);    //調用__temp0的析構函數
 X::~X(yy);      //調用yy的析構函數
 
 既然咱們已經聲明瞭yy,爲何還要緊接着聲明__temp0,其實這裏徹底以把yy和臨時變量合一
 優化後,上面的代碼看起來象這個樣子:
 void foo(X& __temp0) //修改foo的聲明爲引用
 {
  X xx;        //聲明xx
  X::X(xx);      //調用xx的默認構造函數

  __temp0::X::X(xx); //調用__temp0的拷貝構造函數
  X::~X(xx);     //調用xx的析構函數
 }
 
 X yy;         //聲明yy
 foo(yy);       //調用foo
 X::~X(yy);      //調用yy的析構函數
 
 嗯,怎麼說呢,這算是一種優化算法吧,其實這各個技巧已經很是廣泛了,並擁有一個專門的名稱Named Return Value(NRV)優化
 NRV優化現在被視爲標準C++編譯器的一個責無旁貸的優化操做(雖然其需求其實超出了正式標???M?@?? ?"@?以外)

  除了以類爲參數之外,若是參數的類型是const T&類型,這也可能致使臨時變量
  void fun(const string& str)
  const char* name = "wgs";
 fun(name);
  嗯,還記得在const文檔中的論述嗎?對於這種特殊的參數類型,編譯器是很樂意爲你作自動轉換的工做的,代價嘛,就是一個臨時變量,
  不過若是是你本身去作,大概就只能聲明一個局部變量了
 
 爲何函數和臨時變量這麼有緣,其實根本的緣由在於對象傳值的語意,這一個也是爲何C++中鼓勵傳對象地址的緣由
 
 和函數的狀況相似的,還有一大類狀況是臨時變量的樂土,那就是表達式
 string s,t;
 printf("%s", s + t);
 這裏s+t的結果該放在什麼地方呢?只能是臨時變量中.
 
 這個printf語句帶來了新的問題,那就是"臨??M?@?? ?"@時變量的生命期"是如何的?
 對於函數的狀況,咱們已經看到了,臨時變量在完成交換內容的使命後都是儘可能早的被析構了,那麼對於表達式呢?
 若是在s+t計算後析構,那麼print函數打印的就是一個非法內容了,所以C++給出的規則是:
 臨時變量應該在致使臨時變量建立的"完整表達式"求值過程的最後一個步驟被析構
 什麼又是"完整表達式"?簡單的說,就是否是表達式的子表達式
 這條規則聽起來很簡單,但具體實現起來就很是的麻煩了,例如:
 X foo(int n)
 if (foo(1) || foo(2) || foo(3) )
 其中X中有operator int()轉換,因此能夠用在if語句中
 這裏的foo(1)將產生一個臨時變量1,若是這部分爲false,foo(2)將繼續產生一個臨時變量,若是這部分也爲false,foo(3)...
 一個臨時變量的參數竟然是和運行時相關的,更要命的???M?@?? ?"@??你要記住你到底產生了幾個臨時變量並在這個表達式結束的時候進行析構以當心的維護對象構造和析構的一致
 我猜測,這裏會展開成一段複雜的代碼,並加入更多的if判斷才能搞定,呵呵,好在我不是作編譯器的
 
 上面的規則其實還有兩條例外:
 string s,t;
 string v = 1 ? s + t : s - t;
 這裏完整表達式是?語句,可是在完整表達式結束之後臨時變量還不能當即銷燬,而必須在變量v賦值完成後才能銷燬,這就是例外規則1:
 凡含有表達式執行結果的臨時變量,應該存留到對象的初始化操做完成後銷燬
 
 string s,t;
 string& v = s + t;
 這裏s+t產生的臨時變量即便在變量v的賦值完成後也不能銷燬,不然這個引用就沒用了,這就是例外規則2:
 若是一個臨時變量被綁定到一個引用,這個臨時變量應該留到這個臨???M?@?? ?"@??變量和這個引用那個先超出變量的做用域後才銷燬
 
 這篇文章可能有些深奧了,畢竟大多數內容來自於<<Inside The C++ Object Model>>
 那麼就留下一條忠告:
 在stl中,如下的代碼是錯誤的
 string getName();
 char* pTemp = getName().c_str();
 getName返回的就是一個臨時變量,在把它內部的char指針賦值給pTemp後析構了,這時pTemp就是一個非法地址
 確實如C++發明者Bjarne Stroustrup所說,這種狀況通常發生在不一樣類型的相互轉換上
 
 在Qt中,相似的代碼是這樣的
 QString getName();
 char* pTemp = getName().toAscii().data();
 這時pTemp是非法地址
 
 但願你們不要犯相似的錯誤

--------------------------------------------------------------------------
C++中的臨時對象
 
程序員間交談時,常常把???M?@?? ?"@?僅須要一小段時間的變量稱爲臨時變量。例如在下面這段swap(交換)例程裏:
template<class T>
void swap(T& object1, T& object2)
{
 T temp = object1;
 object1 = object2;
 object2 = temp;
}
    
    一般把temp叫作臨時變量。不過就C++而言,temp根本不是臨時變量,它只是一個函數的局部對象。
    在C++中真正的臨時對象是看不見的,它們不出如今咱們的源代碼中。創建一個沒有命名的非堆(non-heap)對象會產生臨時對象。這種未命名的對象一般在兩種條件下產生:爲了使函數成功調用而進行隱式類型轉換和函數返回對象時。理解如何和爲何創建這些臨時對象是很重要的,由於構造和釋放它們的開銷對於程序的性能來講有着不可忽視的影響。
    
    首先考慮爲使函數成功調???M?@?? ?"@??而創建臨時對象這種狀況。當傳送給函數的對象類型與參數類型不匹配時會產生這種狀況。例如一個函數,它用來計算一個字符在字符串中出現的次數:
// 返回ch在str中出現的次數
size_t countChar(const string& str, char ch);
char buffer[MAX_STRING_LEN];
char c;
// 讀入到一個字符和字符串中,用setw避免緩存溢出,當讀取一個字符串時
cin >> c >> setw(MAX_STRING_LEN) >> buffer;
cout << "There are " << countChar(buffer, c)
     << " occurrences of the character " << c
<< " in " << buffer << endl;
    
    看一下countChar的調用。第一個被傳送的參數是字符數組,可是對應函數的正被綁定的參數的類型是const string&。僅當消除類型不匹配後,才能成功進行這個調用,編譯器很???M?@?? ?"@??意替你消除它,方法是創建一個string類型的臨時對象。經過以buffer作爲參數調用string的構造函數來初始化這個臨時對象。countChar的參數str被綁定在這個臨時的string對象上。當countChar返回時,臨時對象自動釋放。
    
    這樣的類型轉換很方便(儘管很危險),可是從效率的觀點來看,臨時string對象的構造和釋放是沒必要要的開銷。一般有兩個方法能夠消除它。一種是從新設計代碼,不讓發生這種類型轉換。另外一種方法是經過修改軟件而再也不須要類型轉換。
    
    僅當經過傳值(by value)方式傳遞對象或傳遞常量引用(reference-to-const)參數時,纔會發生這些類型轉換。當傳遞一個很是量引用(reference-to-non-const)參數對象,就不會發生。考慮一下這個函數:
void uppercasify(string& str);     &nb??M?@?? ?"@sp;         // 把str中全部的字符
// 改變成大寫
    
    在字符計數的例子裏,可以成功傳遞char數組到countChar中,可是在這裏試圖用char數組調用upeercasify函數,則不會成功:
char subtleBookPlug[] = "Effective C++";
uppercasify(subtleBookPlug);                // 錯誤!
    
    沒有爲使調用成功而創建臨時對象,爲何呢?
    假設創建一個臨時對象,那麼臨時對象將被傳遞到upeercasify中,其會修改這個臨時對象,把它的字符改爲大寫。可是對subtleBookPlug函數調用的真正參數沒有任何影響;僅僅改變了臨時從subtleBookPlug生成的string對象。無疑這不是程序員所但願的。程序員傳遞subtleBookPlug參數到uppercasify函數中,指望???M?@?? ?"@?改subtleBookPlug的值。當程序員指望修改非臨時對象時,對很是量引用(references-to-non-const)進行的隱式類型轉換卻修改臨時對象。這就是爲何C++語言禁止爲很是量引用(reference-to-non-const)產生臨時對象。這樣很是量引用(reference-to-non-const)參數就不會遇到這種問題。
    
    創建臨時對象的第二種環境是函數返回對象時。例如operator+必須返回一個對象,以表示它的兩個操做數的和。例如給定一個類型Number,這種類型的operator+被這樣聲明:
const Number operator+(const Number& lhs,const Number& rhs);
    
    這個函數的返回值是臨時的,由於它沒有被命名;它只是函數的返回值。必須爲每次調用operator+構造和釋放這個對象而付出代價。
    
    一般咱們不想付出這樣的開銷。對於這種???M?@?? ?"@??數,能夠切換到operator=,而避免開銷。不過對於大多數返回對象的函數來講,沒法切換到不一樣的函數,從而沒有辦法避免構造和釋放返回值。至少在概念上沒有辦法避免它。然而概念和現實之間又一個黑暗地帶,叫作優化,有時能以某種方法編寫返回對象的函數,以容許編譯器優化臨時對象。這些優化中,最多見和最有效的是返回值優化。
    
    綜上所述,臨時對象是有開銷的,因此應該儘量地去除它們,然而更重要的是訓練本身尋找可能創建臨時對象的地方。在任什麼時候候只要見到常量引用(reference-to-const)參數,就存在創建臨時對象而綁定在參數上的可能性。在任什麼時候候只要見到函數返回對象,就會有一個臨時對象被創建(之後被釋放)。學會尋找這些對象構造,就能顯著地加強透過編譯器表面動做而看到其背後開銷的能力。

-------------------------------------------------
html

什麼是臨時對象?android

        C++真正的臨時對象是不可見的匿名對象,不會出如今你的源碼中,可是程序在運行時確實生成了這樣的對象.ios

一般出如今如下兩種狀況:c++

(1)爲了使函數調用成功而進行隱式類型轉換的時候git

        傳遞某對象給一個函數,而其類型與函數的形參類型不一樣時,若是能夠經過隱式轉化的話可使函數調用成功,那麼此時會經過構造函數生成一個臨時對象,當函數返回時臨時對象即自動銷燬。以下例:程序員

  countChar ( & str, 
 countChar (buffer, c);

      咱們看的第一個參數爲char[],而函數的參數類型爲const string&,參數不一致,看看可否進行隱式轉化,string類有個構造函數是能夠做爲隱式轉化函數(參見5)的。那麼編譯器會產生一個 string的臨時變量,以buffer爲參數進行構造,那麼countChar中的str參數會綁定到此臨時變量上,直到函數返回時銷燬。github

      注意這樣的轉化只會出如今兩種狀況下:函數參數以傳值(by value)的方式傳遞 或者 對象被傳遞到一個 reference-to-const 參數上。web

傳值方式:算法

 countChar ( str,  
countChar (buffer, c);

       這種方法會調用string的拷貝構造函數生成一個臨時變量,再將這個臨時變量綁定到str上,函數返回時進行銷燬。數組

傳常量引用:

       開始的實例即時屬於這種狀況,但必定強調的是傳遞的是const型引用,如將開始函數的原型改成

int countChar (string& str, char ch);

       下面調用相同,編譯器會報錯!爲何C++設計時要求當對象傳遞給一個reference-to-non-const 參數不會發生隱式類型轉化呢?

       下面的實例可能向你說明這樣設計的目的:

 
 toUpper (& buffer[] =

        若是編譯器容許上面的傳遞完成,那麼,會生成一個臨時對象,toUpper函數將臨時變量的字符轉化爲大寫,返回是銷燬對象,可是對buffer內容毫無影響!程序設計的目地是指望對「非臨時對象」進行修改,而若是對reference-to-non-cosnt對象進行轉化,函數只會對臨時變量進行修 改。這就是爲何C++中要禁止non-const-reference參數產生臨時變量的緣由了。

(2)當函數返回對象的時候

        當函數返回一個對象時,編譯器會生成一個臨時對象返回,如聲明一個函數用來合併兩個字符串:

const string strMerge (const string s1, const string s2);
大多時候是沒法避免這樣的臨時變量產生的,可是現代編譯器能夠將這樣的臨時變量進行優化掉,這樣的優化策略中,有個所謂的「返回值優化」,下一篇具體講解。
 總結:
臨時對象有構造和析構的成本,影響程序的效率,所以儘量地消除它們。而更爲重要的是很快地發現什麼地方會生成臨時對象:
  • 當咱們看到一個reference-to-const參數時,很可能一個臨時對象綁定到該參數上;

  • 當咱們看到函數返回一個對象時,就會產生臨時對象。

參考:http://www.cnblogs.com/hazir/archive/2012/04/18/2456144.html 

C++中的返回值優化(return value optimization)

返回值優化(Return Value Optimization,簡稱RVO),是這麼一種優化機制:當函數須要返回一個對象的時候,若是本身建立一個臨時對象用戶返回,那麼這個臨時對象會消 耗一個構造函數(Constructor)的調用、一個複製構造函數的調用(Copy Constructor)以及一個析構函數(Destructor)的調用的代價。而若是稍微作一點優化,就能夠將成本下降到一個構造函數的代價,下面是 在Visual Studio 2008的Debug模式下作的一個測試:(在GCC下測試的時候可能編譯器本身進行了RVO優化,看不到兩種代碼的區別) 

複製代碼

// C++ Return Value Optimization
// 做者:代碼瘋子
// 博客:http://www.programlife.net/
#include <iostream>
using namespace std;
class Rational
{
public:
    Rational(int numerator = 0int denominator = 1) : n(numerator), d(denominator) {
          cout << "Constructor Called..." << endl;
      }
      ~Rational() {
          cout << "Destructor Called..." << endl;
      }
      Rational(const Rational& rhs) {
          this->d = rhs.d;
          this->n = rhs.n;
          cout << "Copy Constructor Called..." << endl;
      }
      int numerator() const { return n; }
      int denominator() const { return d; }
private:
    int n, d;
}; 
const Rational operator*(const Rational& lhs, const Rational& rhs) {
    cout << "----------- Enter operator* -----------" << endl;
    Rational tmp(lhs.numerator() * rhs.numerator(),
        lhs.denominator() * rhs.denominator());
    cout << "----------- Leave operator* -----------" << endl;
    return tmp;
}
int main(int argc, char **argv) {
    Rational x(15), y(29);
    Rational z = x * y;
    cout << "calc result: " << z.numerator() 
        << "/" << z.denominator() << endl;
 
    return 0;
}

複製代碼

函數輸出截圖以下:
Return Value Optimization
能夠看到消耗一個構造函數(Constructor)的調用、一個複製構造函數的調用(Copy Constructor)以及一個析構函數(Destructor)的調用的代價。

而若是把operator*換成另外一種形式:

const Rational operator*(const Rational& lhs,const Rational& rhs)
{
    return Rational(lhs.numerator() * rhs.numerator(),
                lhs.denominator() * rhs.denominator());
}

就只會消耗一個構造函數的成本了:
返回值優化

參考:http://www.programlife.net/cpp-return-value-optimization.html

返回值優化(RVO)與具命返回值優化(NRVO)

這是一項編譯器作的優化,已是一種很常見的優化手段了,搜一下能夠找到不少的資料,在MSDN 裏也有相關的說明。

返回值優化,顧名思義,就是與返回值有關的優化,是當函數是按值返回(而不是引用、指針)時,爲了不產生沒必要要的臨時對象以及值拷貝而進行的優化。

先看看下面的代碼:

複製代碼

typedef unsigned int UINT32;
class MyCla
{
public:
    MyCla(UINT32 a_size = 10):size(a_size) {
        p = new UINT32[size];        
    }
    MyCla(MyCla const & a_right):size(a_right.size) {
        p = new UINT32[size];
        memcpy(p, a_right.p, size*sizeof(UINT32));
    }
    MyCla constoperator = (MyCla const & a_right) {
        size = a_right.size;
        p = new UINT32[size];
        memcpy(p, a_right.p, size*sizeof(UINT32));
        return *this;
    }
    ~MyCla() {
        delete [] p;
    }
private:
    UINT32 *p;
    UINT32 size;
};
MyCla TestFun() {
    return MyCla();
}
int _tmain(int argc, _TCHAR* argv[])
{
    MyCla a = TestFun();   
    return 0;
}

複製代碼

TestFun() 函數返回了一個 MyCla 對象,並且是按值傳遞的。

在沒有任何「優化」以前,這段代碼的行爲也許是這樣的:return MyCla() 這行代碼中,構造了一個 MyCla 類的臨時的無名對象(姑且叫它t1),接着把 t1 拷貝到另外一塊臨時對象 t2(不在棧上),而後函數保存好 t2 的地址(放在 eax 寄存器中)後返回,TestFun 的棧區間被「撤消」(這時 t1 也就「沒有」了,t1 的生存域在 TestFun 中,因此被析構了),在 MyCla a = TestFun(); 這一句中,a 利用 t2 的地址,能夠找到 t2 進行,接着進行構造。這樣 a 的構造過程就完成了。而後再把 t2 也「幹掉」。

能夠看到, 在這個過程當中,t1 和 t2 這兩個臨時的對象的存在實在是很浪費的,佔用空間不說,關鍵是他們都只是爲a的構造而存在,a構造完了以後生命也就終結了。既然這兩個臨時的對象對於程序 員來講根本就「看不到、摸不着」(匿名對象),因而編譯器乾脆在裏面作點手腳,不生成它們!怎麼作呢?很簡單,編譯器「偷偷地」在咱們寫的TestFun 函數中增長一個參數 MyCla&,而後把 a 的地址傳進去(注意,這個時候 a 的內存空間已經存在了,但對象尚未被「構造」,也就是構造函數尚未被調用),而後在函數體內部,直接用a來代替原來的「匿名對象」,在函數體內部就完 成a的構造。這樣,就省下了兩個臨時變量的開銷。這就是所謂的「返回值優化」!在 VC7 裏,按值返回匿名對象時,默認都是這麼作。

上 面說的是「返回值優化(RVO)」,還有一種「具名返回值優化(NRVO)」,是對於按值返回「具名對象」(就是有名字的變量!)時的優化手段,其實道理 是同樣的,但因爲返回的值是具名變量,狀況會複雜不少,因此,能執行優化的條件更苛刻,在下面三種狀況下(來自 MSDN),NRVO 將必定不起做用:

  1. 不一樣的返回路徑上返回不一樣名的對象(好比if XXX 的時候返回x,else的時候返回y)

  2. 引入 EH 狀態的多個返回路徑(就算全部的路徑上返回的都是同一個具名對象)

  3. 在內聯asm語句中引用了返回的對象名。

不過就算 NRVO 不能進行,在上面的描述中的 t2 這個臨時變量也不會產生,對於 VC 的 C++ 編譯器來講,只要你寫的程序是把對象按值返回的,它會有兩種作法,來避免 t2 的產生。拿下面這個程序來講明:

MyCla TestFun2() {
    MyCla x(3);
    return x;
}

一種作法是像 RVO同樣,把做爲表達式中獲取返回值來進行構造的變量 a 當成一個引用參數傳入函數中,而後在返回語句以前,用要返回的那個變量來拷貝構造 a,而後再把這個變量析構,函數返回原調用點,a 就構造好了。

還有一種方式, 是在函數返回的時候,不析構x,而直接把x的地址放到 exa 寄存器中,返回調到 TestFun2 的調用點上,這時,a 能夠用 exa 中存着的地址來進行構造,a 構造完成以後,再析構原來的變量 x !是的,注意到其實這時,x 的生存域已經超出了TestFun2,但因爲這裏x所在TestFun2的棧雖然已經無效,可是並無誰去擦寫這塊存,因此x其實仍是有效的,固然,一切 都在彙編的層面,對於C++語言層面來說是透明的。

 參考:http://www.cnblogs.com/liyiwen/archive/2009/12/02/1615711.html


#include <stdlib.h>
#include <iostream>
#include <string>
#include <iomanip>
//http://www.android100.org/html/201501/31/107059.html
using namespace std;
class Test
{
public:
    //默認構造函數
    Test() :num_(0){
        cout<<setiosflags(ios_base::hex)<<this<< "  default..." << endl;
    }        
    //重載構造函數
    Test(int num):num_(num) {
        cout<<setiosflags(ios_base::hex)<<this<< "  with paramger..."<< endl;
    }
    //析構函數;
    ~Test(){  cout<<setiosflags(ios_base::hex)<<this<< "  destructor..."<< endl; }
     //拷貝構造函數
    Test(const Test &other) :num_(other.num_) {
        cout<<setiosflags(ios_base::hex)<<"LV: "<< this<< " ,Rv: "<<&other <<"  copy..." <<endl;
    }
    //=號運算符重載
    Test & operator=(const Test &other){
        cout<<setiosflags(ios_base::hex)<<"LV: "<< this<< " ,Rv: "<<&other <<"  assign..." <<endl;
        if( this == &other )
            return *this;
        num_ = other.num_;
        return *this;
    }
private:

    int num_;
};
//當形參是對象,而非引用時,要調用拷貝構造函數
//(1) 當函數的形參是類對象時,這時須要在內存中創建一個局部的對象,                                                                                系統要將實參的內容拷貝至形參中,至關於用一個對象來初始化另外一個對象,理所固然的要調用拷貝構造函數。
void TestFun(const Test t){ }
//形參爲引用,不調用拷貝構造函數
void TestFun2(const Test &t ) { }
//函數返回爲對象,而非引用,調用拷貝構造函數
Test TestFun3(const Test &t ){
    cout<<"++++++++++++++++++++++++++"<<endl;
    return t ; //                                                                                                                                 當函數返回時,會建立一個臨時對象,用t初始化這個臨時對象,臨時對象在return後若是沒有人接管,它將馬上被銷燬。須要注意:此時返回的t與實際返回的Test對
象,他們不是同一個對象,實際返回的是臨時建立的對象。
}
// (2)                                                                                                                                            當函數的返回值是類對象時,也要創建一個臨時的對象,要將函數返回時的局部類對象的內容拷貝至臨時對象的內容中,至關於用一個對象來初始化另外一個對象,這種
狀況也要調用拷貝構造函數。可是,爲何這時候系統會建立一個臨時的對象呢?爲何不直接返回局部類對象呢?由於局部類對象在離開創建它的函數時就消亡了,
不可能在返回調用函數後繼續生存。因此編譯系統會在調用函數的表達式中建立一個無名的臨時對象,該臨時對象的生存週期只在函數調用的表達式中。所謂return對
象,其實是調用拷貝構造函數把該對象的值拷入臨時對象。若是返回的是變量,處理過程是相似的,只不過不調用構造函數。
/*因此,建議將函數的形參與返回值對象聲明爲對象的引用,而不是對象,這樣能夠減小內存的使用,不只節省空間,效率也更高。
前面說過,當類中沒有建立任何一個構造函數時,系統就會自動爲咱們建立一個不帶參數的默認構造函數,可是哪怕建立了一個構造函數,系統就再也不爲咱們建立默認
的構造函數了,同理,拷貝構造函數也相似,若是咱們在類中沒有建立一個拷貝構造函數,則系統會爲咱們自動建立一個默認的拷貝構造函數,若是咱們本身建立了拷
貝構造函數,系統就再也不爲咱們建立了。*/
const Test & TestFun4(const Test & t) { return t; }

int main()
{
    Test t(10); //調用帶一個參數的構造函數
    cout<<"0********************"<<endl;
    TestFun(t);//值傳遞,實參對象初始化形參對象,調用拷貝構造函數。
    cout<<"1********************"<<endl;
    TestFun2(t);//引用傳遞,不分配空間,再也不調用拷貝構造函數,減小內存的複製操做。
    cout<<"2********************"<<endl;
    t = TestFun3(t); //函數在執行時也調用拷貝構造函數,函數返回一個臨時對象,最後是對象之間的等號賦值操做,即調用operator=(),                     賦值成功後,臨時對象沒人接管,被銷燬。
    cout<<"3********************"<<endl;

    Test t2 = TestFun3(t); //本質是初始化,把臨時對象初始化到t2,至關於把臨時對象改名爲t2,即臨時對象獲得接管,因此這時臨時對象不會被銷燬。
    cout<<"4********************"<<endl;

    const Test &t3 =  TestFun3(t);//引用要指向臨時對象,此時臨時對象也獲得接管,因此這時臨時對象不會被銷燬。
    cout<<"5********************"<<endl;

    TestFun3(t); //返回的臨時對象沒人接管,因此馬上被銷燬。
    cout<<"6********************"<<endl;
    Test t4 = TestFun4(t); //建立t4對象時,TestFun4()返回的是引用對象,這個引用對象要初始化到t4,                                                  即用一個對象來初始化另外一個對象,本質上仍是要調用拷貝構造函數,這種狀況與TestFun3()是不同的,注意區分。
    cout<<"7********************"<<endl;
    const Test &t5 = TestFun4(t); //t5引用指向t,不調用拷貝構造函數。
    cout<<"8********************"<<endl;

    return 0;    
}


http://tanglei.me/reading%20notes/more-effective-cpp-notes.htmlhttp://www.cppfans.org/1397.htmlhttp://www.ituring.com.cn/article/39533http://www.android100.org/html/201501/31/107059.html?WebShieldSessionVerify=Q5zPudazdBXxe3yIM2gKhttp://www.uml.org.cn/c++/201204164.asphttp://ibillxia.github....http://www.kaka-ace.com/cpp11-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-11/http://blog.csdn.net/flyingleo1981/article/details/16842279http://www.yanyulin.info/pages/2015/01/18747611937191.htmlhttp://www.cnblogs.com/haippy/p/3236136.htmlhttp://www.ituring.com.cn/article/39533http://www.uml.org.cn/c++/201204164.asphttp://ibillxia.github.io/blog/2014/06/08/stl-source-insight-0-stl-overview/http://ibillxia.github.io/blog/2014/06/13/stl-source-insight-1-memory-allocator/http://ibillxia.github.io/blog/2014/06/21/stl-source-insight-2-iterators-and-traits/

相關文章
相關標籤/搜索