隨筆-204  評論-149  文章-0  trackbacks-0
          Effective C++
          條款31: 千萬不要返回局部對象的引用,也不要返回函數(shù)內(nèi)部用new初始化的指針的引用


           1 #include<iostream>
           2 #include<string>
           3 
           4 using namespace std;
           5 
           6 class SimpleCat
           7 {
           8 public:
           9     SimpleCat(void);
          10     SimpleCat(int age,int weight);
          11     int GetAge(){return itsAge;}
          12     int GetWeight(){return itsWeight;}
          13 
          14 private:
          15     int itsAge;
          16     int itsWeight;
          17     //int itsAge=5;//1>.\SimpleCat.cpp(14) : error C2864: “SimpleCat::itsAge”: 只有靜態(tài)常量整型數(shù)據(jù)成員才可以在類中初始化
          18 
          19 public:
          20     virtual ~SimpleCat(void);
          21 };
          22 
          23 SimpleCat::SimpleCat(void)
          24 {
          25     cout<<"SimpleCat constructor "<<this<<endl;
          26 }
          27 
          28 SimpleCat::SimpleCat(int age,int weight)
          29 {
          30     cout<<"SimpleCat constructor "<<this<<endl;
          31     itsAge = age;
          32     itsWeight = weight;
          33 }
          34 
          35 SimpleCat::~SimpleCat(void)
          36 {
          37     cout<<"SimpleCat destructor"<<this<<endl;
          38 }
          39 
          40 SimpleCat &TheFunction();
          41 
          42 int main()
          43 {
          44     SimpleCat myCat;
          45     cout<<myCat.GetAge()<<endl;//這個值區(qū)別于java不是賦初值為0的,而是一個隨機(jī)的值
          46 //    cout<<myCat.itsAge<<endl;
          47 
          48     cout<<"------------------------"<<endl;
          49     SimpleCat &rCat = TheFunction();
          50     int age = rCat.GetAge();
          51     cout<<"rCat is "<<age<<"yeas old!"<<endl;
          52     cout<<"&rCat:  "<<&rCat<<endl;
          53     SimpleCat *pCat = &rCat;
          54     //delete rCat;//不能對引用使用delete
          55     delete pCat;
          56     //delete好像沒有釋放內(nèi)存,怎么獲取的還是原來的值
          57     //可能在這個內(nèi)存區(qū)域存放的還是原來的?先new string后再調(diào)用也沒變,與編譯器有關(guān)還是什么??
          58     for(int i =0;i<10;i++)
          59     {
          60         //想通過創(chuàng)建string對象來填充之前的內(nèi)存區(qū)間,好像沒用
          61         string *= new string("abcdefghijklmn");
          62     }
          63 
          64     //這時問題來了,rCat.getAge()為123了
          65     SimpleCat *pSecond = new SimpleCat(123,444);
          66     
          67     cout<<"delete pCat后再使用rCat引用會發(fā)生什么問題???"<<endl;
          68     cout<<"delete pCat后 &rCat"<<&rCat<<endl;
          69     cout<<"delete pCat后 rCat.age"<<rCat.GetAge()<<endl;
          70 
          71     cout<<"--------------------------"<<endl;
          72     SimpleCat myCat2 = TheFunction();//這個會發(fā)生內(nèi)存泄漏,在函數(shù)中申請的內(nèi)存會得不到釋放
          73                                      //myCat2是通過默認(rèn)的拷貝函數(shù)來進(jìn)行初始化的
          74     cout<<"myCat2的地址是 "<<&myCat2<<endl;
          75     return 0;
          76 }
          77 
          78 
          79 //這個函數(shù)在返回時,是否會創(chuàng)建一個臨時引用變量???
          80 //TheFunctionTwo(SimpleCat &simpleCat)這個函數(shù)在傳遞參數(shù)時是否會創(chuàng)建一個臨時的引用變量
          81 //臨時的引用變量的作用域范圍是什么
          82 SimpleCat &TheFunction()
          83 {
          84     SimpleCat *pFrisky = new SimpleCat(5,9);
          85     cout<<"pFrisky: "<<pFrisky<<endl;
          86     return *pFrisky;
          87 }
          88 
          89 
          90 

          函數(shù)TheFunction()的返回值被賦值給對SimpleCat類的一個引用,然后使用這個引用來獲得對象的成員變量age的值
          為了證明函數(shù)TheFunction()在自由存儲區(qū)中創(chuàng)建的對象被賦給了函數(shù)main()聲明的引用,對rCat采取取址運(yùn)算,很明顯,它顯示了其引用的對象的地址,并且和自由存儲區(qū)中的對象的地址相匹配。
          這一切看上去都很正確,但是如何釋放被占用的內(nèi)存,對于引用是不能使用delete運(yùn)算符的,一個筆記聰明的方法是聲明另一個指針,并使用由rCat得到的地址對它進(jìn)行初始化。這樣做確實可以釋放內(nèi)存,避免了內(nèi)存的泄漏,但是存在一個小問題,在delete pCat后,rCat引用又代表什么呢??引用必須是一個實際對象的別名;如果引用一個空對象的話(現(xiàn)在就是這種情況)那么程序就是無效的。。

          注意:不能過分去強(qiáng)調(diào)一個使用對空對象的引用的程序,也行能通過編譯,但是即使程序通過了編譯,它仍然是病態(tài)的,而且執(zhí)行結(jié)果也是不可預(yù)料的

          關(guān)于這個問題,存在這3種解決方案
          第1種是在第49行聲明一個SimpleCat類對象,然后從函數(shù)TheFunction()中采取值傳遞的方式返回它的局部對象,這時不能在自由存儲區(qū)來分配對象空間,不能使用new,
          第2種是在函數(shù)TheFunction()中聲明一個位于自由存儲區(qū)中的SimpleCat類的對象,但是讓函數(shù)返回一個指向內(nèi)存區(qū)域的指針,然后完成對對象的操作,調(diào)用函數(shù)可以刪除這個指針
          第3種可行的方案,也是正確的,是在調(diào)用函數(shù)中聲明對象,然后通過引用的方式把它傳遞給函數(shù)TheFunction()


          也意味在main函數(shù)中不能這樣使用了

          posted on 2009-05-11 23:01 Frank_Fang 閱讀(1149) 評論(2)  編輯  收藏 所屬分類: C++編程

          評論:
          # re: C++函數(shù)返回自由內(nèi)存區(qū)間的對象的引用 2009-06-06 22:06 | yyy
          條款31: 千萬不要返回局部對象的引用,也不要返回函數(shù)內(nèi)部用new初始化的指針的引用

          本條款聽起來很復(fù)雜,其實不然。它只是一個很簡單的道理,真的,相信我。

          先看第一種情況:返回一個局部對象的引用。它的問題在于,局部對象 ----- 顧名思義 ---- 僅僅是局部的。也就是說,局部對象是在被定義時創(chuàng)建,在離開生命空間時被銷毀的。所謂生命空間,是指它們所在的函數(shù)體。當(dāng)函數(shù)返回時,程序的控制離開了這個空間,所以函數(shù)內(nèi)部所有的局部對象被自動銷毀。因此,如果返回局部對象的引用,那個局部對象其實已經(jīng)在函數(shù)調(diào)用者使用它之前被銷毀了。

          當(dāng)想提高程序的效率而使函數(shù)的結(jié)果通過引用而不是值返回時,這個問題就會出現(xiàn)。下面的例子和條款23中的一樣,其目的在于詳細(xì)說明什么時候該返回引用,什么時候不該:

          class rational { // 一個有理數(shù)類
          public:
          rational(int numerator = 0, int denominator = 1);
          ~rational();

          ...

          private:
          int n, d; // 分子和分母

          // 注意operator* (不正確地)返回了一個引用
          friend const rational& operator*(const rational& lhs,
          const rational& rhs);
          };

          // operator*不正確的實現(xiàn)
          inline const rational& operator*(const rational& lhs,
          const rational& rhs)
          {
          rational result(lhs.n * rhs.n, lhs.d * rhs.d);
          return result;
          }

          這里,局部對象result在剛進(jìn)入operator*函數(shù)體時就被創(chuàng)建。但是,所有的局部對象在離開它們所在的空間時都要被自動銷毀。具體到這個例子來說,result是在執(zhí)行return語句后離開它所在的空間的。所以,如果這樣寫:

          rational two = 2;

          rational four = two * two; // 同operator*(two, two)


          函數(shù)調(diào)用時將發(fā)生如下事件:

          1. 局部對象result被創(chuàng)建。
          2. 初始化一個引用,使之成為result的另一個名字;這個引用先放在另一邊,留做operator*的返回值。
          3. 局部對象result被銷毀,它在堆棧所占的空間可被本程序其它部分或其他程序使用。
          4. 用步驟2中的引用初始化對象four。

          一切都很正常,直到第4步才產(chǎn)生了錯誤,借用高科技界的話來說,產(chǎn)生了"一個巨大的錯誤"。因為,第2步被初始化的引用在第3步結(jié)束時指向的不再是一個有效的對象,所以對象four的初始化結(jié)果完全是不可確定的。

          教訓(xùn)很明顯:別返回一個局部對象的引用。

          "那好,"你可能會說,"問題不就在于要使用的對象離開它所在的空間太早嗎?我能解決。不要使用局部對象,可以用new來解決這個問題。"象下面這樣:

          // operator*的另一個不正確的實現(xiàn)
          inline const rational& operator*(const rational& lhs,
          const rational& rhs)
          {
          // create a new object on the heap
          rational *result =
          new rational(lhs.n * rhs.n, lhs.d * rhs.d);

          // return it
          return *result;
          }

          這個方法的確避免了上面例子中的問題,但卻引發(fā)了新的難題。大家都知道,為了在程序中避免內(nèi)存泄漏,就必須確保對每個用new產(chǎn)生的指針調(diào)用delete,但是,這里的問題是,對于這個函數(shù)中使用的new,誰來進(jìn)行對應(yīng)的delete調(diào)用呢?

          顯然,operator*的調(diào)用者應(yīng)該負(fù)責(zé)調(diào)用delete。真的顯然嗎?遺憾的是,即使你白紙黑字將它寫成規(guī)定,也無法解決問題。之所以做出這么悲觀的判斷,是基于兩條理由:

          第一,大家都知道,程序員這類人是很馬虎的。這不是指你馬虎或我馬虎,而是指,沒有哪個程序員不和某個有這類習(xí)性的人打交道。想讓這樣的程序員記住無論何時調(diào)用operator*后必須得到結(jié)果的指針然后調(diào)用delete,這樣的幾率有多大呢?也是說,他們必須這樣使用operator*:

          const rational& four = two * two; // 得到廢棄的指針;
          // 將它存在一個引用中
          ...

          delete &four; // 得到指針并刪除

          這樣的幾率將會小得不能再小。記住,只要有哪怕一個operator*的調(diào)用者忘了這條規(guī)則,就會造成內(nèi)存泄漏。

          返回廢棄的指針還有另外一個更嚴(yán)重的問題,即使是最盡責(zé)的程序員也難以避免。因為常常有這種情況,operator*的結(jié)果只是臨時用于中間值,它的存在只是為了計算一個更大的表達(dá)式。例如:

          rational one(1), two(2), three(3), four(4);
          rational product;

          product = one * two * three * four;

          product的計算表達(dá)式需要三個單獨(dú)的operator*調(diào)用,以相應(yīng)的函數(shù)形式重寫這個表達(dá)式會看得更清楚:

          product = operator*(operator*(operator*(one, two), three), four);

          是的,每個operator*調(diào)用所返回的對象都要被刪除,但在這里無法調(diào)用delete,因為沒有哪個返回對象被保存下來。

          解決這一難題的唯一方案是叫用戶這樣寫代碼:

          const rational& temp1 = one * two;
          const rational& temp2 = temp1 * three;
          const rational& temp3 = temp2 * four;

          delete &temp1;
          delete &temp2;
          delete &temp3;

          果真如此的話,你所能期待的最好結(jié)果是人們將不再理睬你。更現(xiàn)實一點(diǎn),你將會在指責(zé)聲中度日,或者可能會被判處10年苦力去寫威化餅干機(jī)或烤面包機(jī)的微代碼。

          所以要記住你的教訓(xùn):寫一個返回廢棄指針的函數(shù)無異于坐等內(nèi)存泄漏的來臨。

          另外,假如你認(rèn)為自己想出了什么辦法可以避免"返回局部對象的引用"所帶來的不確定行為,以及"返回堆(heap)上分配的對象的引用"所帶來的內(nèi)存泄漏,那么,請轉(zhuǎn)到條款23,看看為什么返回局部靜態(tài)(static)對象的引用也會工作不正常。看了之后,也許會幫助你避免頭痛醫(yī)腳所帶來的麻煩。
            回復(fù)  更多評論
            
          # re: C++函數(shù)返回自由內(nèi)存區(qū)間的對象的引用 2009-06-06 22:24 | yyy
          const rational& four = two * two; // 得到廢棄的指針;
          // 將它存在一個引用中

          還必須得使用引用,否則會發(fā)生內(nèi)存泄漏
            回復(fù)  更多評論
            
          主站蜘蛛池模板: 民权县| 新乐市| 瓦房店市| 聂拉木县| 乐东| 浮山县| 荆州市| 隆林| 松原市| 新沂市| 罗源县| 松江区| 淳安县| 漳浦县| 阜阳市| 尚志市| 凌海市| 隆德县| 九龙县| 定陶县| 和林格尔县| 习水县| 宜城市| 沧源| 遵化市| 泰和县| 麦盖提县| 临沭县| 西乌珠穆沁旗| 镇康县| 焉耆| 南丹县| 沂源县| 祥云县| 交城县| 枞阳县| 屏南县| 罗山县| 荆门市| 延津县| 岳西县|