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