1. 程式人生 > >為什麼C++中千萬不要返回區域性物件或變數的引用和指標

為什麼C++中千萬不要返回區域性物件或變數的引用和指標

大家都知道一個常識:“C++中千萬不要返回區域性物件或變數的引用和指標”。

既然所有C++權威的書上都要求“一定不要返回區域性物件或變數的引用和指標”,那為什麼C++編譯器不從語法上直接禁掉這種用法。如果只是建議的話,那麼“返回區域性物件或變數的引用和指標”是否有用武之地呢?(從理論上來講,我認為這種做法似乎總是錯誤的,原因大家都知道。)
例1:
class CComplex

{

public:

         CComplex():real(0),image(0){}

         CComplex(doublereal,double image):real(real),image(image){}

         CComplex& operator+(const CComplex& second)

         {

                   CComplextemp(real+second.real,image+second.image);

                   returntemp;

         }

         voidPrint()

         {

                   cout << "(" << real << "+" << image << "i)" << endl;

         }

private:

         doublereal;

         doubleimage;

};

int main()

{

         CComplex a(2,4);

         CComplex b(1.5,3.5);

         CComplex c=a+b;

         c.Print();

         return0;

}

  operator+返回的是臨時物件的引用,為什麼能正確地工作???

例2
string& f()

{

         string s("hello");

         returns;

}

int main()

{

         cout << f() << endl;

         return0;

}


  同樣是物件,為什麼string物件就不行,就因為string比較特殊???

例子3
double& f()

{

         doubled(5.55);

         returnd;

}

int main()

{

         cout << f() << endl;

         return0;

}

  為什麼內建型別(int,float等均可)返回區域性變數的引用總可以正確地工作???


  operator+返回的是臨時物件的引用,為什麼能正確地工作???
  答:main函式在執行之後,a,b入棧,接著a+b呼叫了operator+,temp也入棧,operator+執行完後,temp出棧並呼叫解構函式,由於出棧僅僅是移動了PC指標,而你又未寫解構函式將CComplex清零,因此temp所佔的那塊棧空間的記憶體依然保持原樣,只是PC指標已經不再指向它,而operator+返回的引用其實指向的是temp所佔記憶體,然後在呼叫CComplex的預設拷貝構造的函式的時候,由於拷貝建構函式的輸入引數也是引用,因此也指向temp那塊記憶體,對此快記憶體也會按照CComplex型別來進行訪問,最後c就得到了temp的內容。這裡即使是寫成CComplex& c=a+b;結果也是能輸出temp的內容的。此時你若在此句話後面再加幾個函式呼叫,這些函式必須要有引數或內部定義有變數,然後再c.Print(),你會發現結果完全變了。

  同樣是物件,為什麼string物件就不行,就因為string比較特殊???
  答:因為s在出棧的時候其解構函式會將記憶體都清掉,在外面還想訪問自然訪問不成功了。

  一、為什麼不禁用的問題
  為什麼不禁引用返回區域性變數,技術上真的是不難嗎?且,有足夠的必要嗎?請見以下例子:

int *f1(int &ri)

{

         return&ri;

}

int *f2()

{

         inti=4;

         int *j;

         j=f1(i);

         returnj;

}

int main()

{

         int*p=f2();

         *p=6;

         return0;

}


  p在初始化後,*p生命期是否已經結束了呢?我相信,如果這件事也得由編譯器去判斷,那麼顯然,程式設計師全部可以下崗了,編譯器實在是太智慧了,人還有必要存在嗎?但現有技術真的能嗎?如果能的話,要花多大開銷,這個開銷有必要嗎?“千萬不要返回區域性物件或變數的引用和指標”應該是個原則性的東西,它是個典型代表,其實大原則是“不要在自動變數(不管是表示式中間結果的臨時變數(如果它不能保證總優化到暫存器中)還是源程式中有明確名字的auto變數)生命期結束後還試圖解引用它”。

  程式設計語言課一般會說語言的可寫性與可讀性是對矛盾,C語言的可寫性特別強,既會給比較強的人非常靈活的選擇,又會讓入門者走不少彎路或者半途而廢。利器不是誰都能用得好,這與水平不水平沒什麼關係,說人的水平不足夠使用C++,當然也可以站在沒有學會用C++的人的立場,說C++太過於複雜,以至大多數人是學不會用不好的,但它的每個設計的確都有它的現實考慮,程式語言是很實在的東西,往往外貌冷冰冰但其為什麼是這樣有充足原因。

  二、你的好運氣

  你要是明白函式呼叫時區域性變數是如何入棧出棧的,看看反彙編的程式碼,並跟蹤一下堆疊的變化情況,你會設計出一個讓值產生變化的例子。如果這類錯誤後,導致被改變的值,並不是指標的值,則在這麼小的程式中,系統不一定都崩潰,它不過是讓部分你沒照顧到的地方變了變值,卻沒有影響輸出。

  建議樓主閱讀一下TCPL有關臨時變數一節,看看各種條件下生成的臨時變數的作用域,與給出名字的區域性變數間,有何差同。

  三、其他一些為什麼的例子

  關於C++的為什麼特別多,如果你不是經驗豐富且善於思考,是很難理解為什麼有這麼多為什麼的。當然,為什麼的多少,是個程度問題,有差異存在的地方就有程度問題,不同的人善用不同的東西,C++是“小眾”的,但還不至於只是幾個人的,畢竟TIOBE還排第3。

  1.operator過載的解析順序為什麼如現在標準那樣設計?是權衡了使用者的方便,和編譯器的效率之間的一種平衡,它過度自由帶來的是呈指數級上升的編譯時開銷,且該開銷並不一定值得。

  2.內建陣列,為什麼不設定下標檢測?如果檢測下標,定然就會在每次訪問下標時,做是否越界的檢驗,這就帶來了執行時開銷。如果你的演算法非常好,定然不需要檢測下標,則語言假定一定要在每次訪問下標時都判斷,就會影響效率並失去選擇的機會。如果設定N個選項,可以用來關閉或開啟是否檢測下標,那不應該是一種語言應該乾的,各有各的側重點。

  3.C語言傳陣列引數為什麼預設是轉換成指標型別?以C語言產生那個年代的硬體條件,複製陣列很奢侈,尤其函式被呼叫往往很頻繁,演算法要儘量往不復制的情況下設計,如果實在必要,非要複製,你也可以手動memcpy嘛!總之它不是預設項。C++給了使用者另一種選項,即通過加上引用,而使得能夠真正傳整個陣列,不過這都是很多年以後的事了。

  4.for語句為什麼有的靈活有的嚴格?像在Ada中的語法,便是禁止迴圈變數被改變,且不能設定步長值,要想達到這兩個目的,便只能用其他變數再過渡,這樣做是為了高度的安全。反之,C語言的for則非常靈活,也沒有Ada那麼多的限制,但這種靈活並不能保證使用者用其寫出錯誤邏輯的程式碼;VB的自由度則介於二者之間。不能因為這些語言的設計不同,而指責其中某一種語言為何不對某一語法特性做必要的限制,它真的必要嗎?個案好說,但綜合全域性,很難評估。

  四、設計者們不傻

  且任何有影響力的技術,其規範,都是經過全球大量從業者多年實踐後,總結整理並論證出來的,並不是一個或幾個人拍拍腦袋就草率決定的,因此C++的新標準化過程要歷時8年之久。Bjarne Stroustrup不傻,Herb Sutter, Stanley Lippman, ScottMeyer, Alexander Stepanov, Andrew Koenig等人也不傻,標準委員會都不是白給的,大多數細節問題早就被提出過。具體實現,要難得多,這點語法層面上的皮毛問題,都不值一提。

注:轉載出自:http://blog.csdn.net/d04421024/article/details/7317456

閱讀(1) | 評論(0) | 轉發(0) | 評論熱議