隨筆-204  評論-149  文章-0  trackbacks-0
          Effective C++
          條款31: 千萬不要返回局部對象的引用,也不要返回函數內部用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”: 只有靜態常量整型數據成員才可以在類中初始化
          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;//這個值區別于java不是賦初值為0的,而是一個隨機的值
          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好像沒有釋放內存,怎么獲取的還是原來的值
          57     //可能在這個內存區域存放的還是原來的?先new string后再調用也沒變,與編譯器有關還是什么??
          58     for(int i =0;i<10;i++)
          59     {
          60         //想通過創建string對象來填充之前的內存區間,好像沒用
          61         string *= new string("abcdefghijklmn");
          62     }
          63 
          64     //這時問題來了,rCat.getAge()為123了
          65     SimpleCat *pSecond = new SimpleCat(123,444);
          66     
          67     cout<<"delete pCat后再使用rCat引用會發生什么問題???"<<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();//這個會發生內存泄漏,在函數中申請的內存會得不到釋放
          73                                      //myCat2是通過默認的拷貝函數來進行初始化的
          74     cout<<"myCat2的地址是 "<<&myCat2<<endl;
          75     return 0;
          76 }
          77 
          78 
          79 //這個函數在返回時,是否會創建一個臨時引用變量???
          80 //TheFunctionTwo(SimpleCat &simpleCat)這個函數在傳遞參數時是否會創建一個臨時的引用變量
          81 //臨時的引用變量的作用域范圍是什么
          82 SimpleCat &TheFunction()
          83 {
          84     SimpleCat *pFrisky = new SimpleCat(5,9);
          85     cout<<"pFrisky: "<<pFrisky<<endl;
          86     return *pFrisky;
          87 }
          88 
          89 
          90 

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

          注意:不能過分去強調一個使用對空對象的引用的程序,也行能通過編譯,但是即使程序通過了編譯,它仍然是病態的,而且執行結果也是不可預料的

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


          也意味在main函數中不能這樣使用了

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

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

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

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

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

          class rational { // 一個有理數類
          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*不正確的實現
          inline const rational& operator*(const rational& lhs,
          const rational& rhs)
          {
          rational result(lhs.n * rhs.n, lhs.d * rhs.d);
          return result;
          }

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

          rational two = 2;

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


          函數調用時將發生如下事件:

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

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

          教訓很明顯:別返回一個局部對象的引用。

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

          // operator*的另一個不正確的實現
          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;
          }

          這個方法的確避免了上面例子中的問題,但卻引發了新的難題。大家都知道,為了在程序中避免內存泄漏,就必須確保對每個用new產生的指針調用delete,但是,這里的問題是,對于這個函數中使用的new,誰來進行對應的delete調用呢?

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

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

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

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

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

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

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

          product = one * two * three * four;

          product的計算表達式需要三個單獨的operator*調用,以相應的函數形式重寫這個表達式會看得更清楚:

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

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

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

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

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

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

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

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

          還必須得使用引用,否則會發生內存泄漏
            回復  更多評論
            
          主站蜘蛛池模板: 泾阳县| 常熟市| 公主岭市| 台中县| 鹰潭市| 县级市| 吉隆县| 宁蒗| 环江| 朝阳市| 哈尔滨市| 汝南县| 句容市| 漯河市| 修武县| 赣州市| 顺昌县| 隆尧县| 云南省| 项城市| 阿尔山市| 黄龙县| 和龙市| 剑河县| 阿拉善左旗| 安达市| 攀枝花市| 彭州市| 清苑县| 桐乡市| 崇左市| 西贡区| 慈溪市| 远安县| 商河县| 剑阁县| 县级市| 丰都县| 溆浦县| 全南县| 闽清县|