條款31: 千萬不要返回局部對象的引用,也不要返回函數內部用new初始化的指標的引用 (轉自effective c++ second edition)

來源:互聯網
上載者:User

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

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

當想提高程式的效率而使函數的結果通過引用而不是值返回時,這個問題就會出現。下面的例子和條款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)對象的引用也會工作不正常。看了之後,也許會協助你避免頭痛醫腳所帶來的麻煩。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.