Effective C++ 11 在operator=中处理“自我赋值” 笔记
2?Widget?w;
3?...
4?w?=?w;?//賦值給自己
???? 這看起來有點愚蠢,但它合法,所以不要認定客戶絕不會那么做。此外賦值動作并不總是那么可被一眼辨認出來,例如:
a[i]?=?a[j];???//潛在的自我賦值如果i和j有相同的值,這便是個自我賦值。再看:
*px?=?*py;???//潛在的自我賦值如果*px和*py恰好指向同一個東西,這也是自我賦值。這寫并不明顯的自我賦值,是“別名”帶來的結果:所謂“別名”就是“有一個以上的方法指稱(指涉)某對象”。一般而言如果某段代碼操作pointers或references而它們被用來“指向多個相同類型的對象”,就需要考慮這些對象是否為同一個。實際上兩個對象只要來自同一個繼承體系,它們甚至不需要聲明為相同類型就可能造成“別名”,因為一個base class的reference或pointer可以指向一個derived class對象:
1?class?Base?{?...?};2?class?Derived:?public?Base?{...};
3?void?doSomething(const?Base&?rb,?Derived*?pd);?//rb和*pd有可能其實是同一對象。
? ?? 如果遵循條款13和條款14的忠告,你會運用對象來管理資源,而且你可以確定所謂“資源管理對象”在copy發生時有正確的舉措。這種情況下你的賦值操作符或許是“自我賦值安全的”,不需要額外操心。然而如果你嘗試自行管理資源(如果你打算寫一個用于資源管理的class就得這樣做),可能會掉進“在停止使用資源之前意外釋放了它”的陷阱。假設你建立一個class用來保存一個指針指向一塊動態分配的位圖(bitmap):
2?class?Widget?{
3???...
4?private:
5???Bitmap*?pb;?????//指針,指向一個從heap分配而得的對象
6?};
???? 下面是operator=實現代碼,表面上看起來合理,但是自我賦值出現時并不安全(它也不具備異常安全性,但我們稍后才討論這個主題)。
1?Widget&?Widget::operator=(const?Widget&?rhs)?//一份不安全的operator=實現版本。2?{
3???delete?pb;?????????????????????????????????//停止使用當前的bitmap
4???pb?=?new?Bitmap(*rhs.pb);??????????????????//使用rhs's?bitmap的副本(復件)。
5???return?*this;??????????????????????????????//見條款10.
6?}
???? 這里的自我賦值問題是,operator=函數內的*this(賦值的目的端)和rhs有可能是同一個對象。果真如此delete就不只是銷毀當前對象的bitmap,它也銷毀rhs的bitmap。在函數末尾,Widget——它原本不該被自我賦值動作改變的——發現自己持有一個指針指向一個已被刪除的對象!
???? 欲阻止這種錯誤,傳統做法是由operator=最前面的一個“證同測試”達到“自我賦值”的檢驗目的:
1?Widget&?Widget::operator=(const?Widget&?rhs)2?{
3???if(this?==?&rhs)?return?*this;???//證同測試:如果是自我賦值就不做任何事。
4?
5???delete?pb;
6???pb?=?new?Bitmap(*rhs.pb);
7???return?*this;
8?}
???? 這樣做行得通。稍早我曾經提過,前一版operator=不僅不具備“自我賦值安全性”,也不具備“異常安全性”,這個新版本仍然存在異常方面的麻煩。更明確地說,如果"new Bitmap”導致異常(不論是因為分配時內存不足或因為Bitmap的copy構造函數拋出異常),Widget最終會持有一個指針指向一塊被刪除的Bitmap。這樣的指針有害。你無法安全地刪除它們,甚至無法安全地讀取它們。唯一能對它們做的安全事情就是付出許多調試能量找出錯誤的起源。
???? 令人高興的是,讓operator=具備“異常安全性”往往自動獲得“自我賦值安全”的回報。因此愈來愈多人對“自我賦值”的處理態度是傾向不去管它,把焦點放在實現“異常安全性”上。條款29深度探討了異常安全性,本條款只要你注意“許多時候一群精心安排的語句就可以導出異常安全(以及自我賦值安全)的代碼”,這就夠了。例如以下代碼,我們只需注意在復制pb所指東西之前別刪除pb:
1?Widget&?Widget::operator=(const?Widget&?rhs)2?{
3???Bitmap*?pOrig?=?pb;?????????//記住原先的pb
4???pb?=?new?Bitmap(*rhs.pb);???//令pb指向*pb的一個復件(副本)
5???delete?pOrig;???????????????//刪除原先的pb
6???return?*this;
7?}
???? 現在,如果“new Bitmap”拋出異常,pb(及其棲身的那個Widget)保持原狀。即使沒有證同測試,這段代碼還是能夠處理自我賦值,因為我們對原bitmap做了一份復件、刪除原bitmap、然后指向新制造的那個復件。它或許不是處理“自我賦值”的最高效辦法,但它行得通。
???? 如果你很關心效率,可以把“證同測試”再次放回函數起始處。然而這樣做之前先問問自己,你估計“自我賦值”的發生頻率有多高?因為這項測試也需要成本。它會使代碼變大一些(包括原始碼和目標碼)并導入一個新的控制流分支,而兩者都會降低執行速度。Prefetching、caching和pipelining的指令的效率都會因此降低。
???? 在operator=函數內手工排列語句(確保代碼不但“異常安全”而且“自我賦值安全”)的一個替代方案是,使用所謂的copy and swap技術。這個技術和“異常安全性”有密切關系,所以由條款29詳細說明。然而由于它是一個常見而夠好的operator=撰寫辦法,所以值得看看其實現手法像什么樣子:
?1?class?Widget?{?2???...
?3???void?swap(Widget&?rhs);??//交換*this和rhs的數據:詳見條款29
?4???...
?5?};
?6?Widget&?Widget::operator=(const?Widget&?rhs)
?7?{
?8???Widget?temp(rhs);???????//為rhs數據制作一份復件(副本)
?9???swap(temp);?????????????//將*this數據和上述復件的數據交換。
10???return?*this;
11?}
???? 這個主題的另一個變奏曲乃利用以下事實:(1)某class的copy assignment操作符可能被聲明為“以by value方式接受實參”;(2)以by vlaue方式傳遞東西會造成一份復件/副本(見條款20):
1?Widget&?Widget::operator=(Widget?rhs)??//rhs是被傳對象的一份復件(副本),注意這里是pass?by?value。2?{
3???swap(rhs);???????????????????????????//將*this的數據和復件/副本的數據互換
4???return?*this;
5?}
???? 我個人比較憂慮這個做法,我認為它為了伶俐巧妙的修補而犧牲了清晰性。然而將“copy 動作”從函數本體內移至“函數參數構造階段”卻可令編譯器有時生成更高效的代碼。
請記住:
1、確保當對象自我賦值時operator=有良好行為。其中技術包括比較“來源對象”和“目標對象”的地址、精心周到的語句順序、以及copy-and-swap。
2、確定任何函數如果操作一個以上的對象,而其中多個對象是同一個對象時,其行為仍然正確。
轉載于:https://www.cnblogs.com/shengjin/archive/2010/01/31/1657899.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的Effective C++ 11 在operator=中处理“自我赋值” 笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Silverlight C# 游戏开发:
- 下一篇: ccna实验配置个人总结