您好,登錄后才能下訂單哦!
所謂的自我賦值,指得就是一個對象賦值給自己的簡單行為,但這種看起來人畜無害動作,在某些情形下卻可能會使得你的代碼崩潰。
自我賦值的語句,就像這樣:
Widget w;
w = w;
很明顯,這是一段愚蠢的代碼。但既然我們提到自我賦值會引發問題,那我們先來澄清一下自我賦值的情況其實有時并不是那么顯而易見的,并不一定都像上述代碼那么愚蠢,它們還可能是這樣:
a[i] = a[j];
*px = *py;
class base { ... };
class derived : base { ... };
void f(derived *p, base &r);
以上代碼中,a[i] 和 a[j] 有可能是同一對象,兩個不同的指針 px 和 py 有可能指向同一對象,而基類引用 r 也完全有可能引用了指針 p 所指向的同一對象。它們這看起來,要比前面的代碼隱蔽多了。
下面來說說,為什么自我賦值會有危險。考慮一個儲存了一張 Jpeg 圖片數據的類:
class Image
{
... ...
private:
Jpeg *p;
};
下面是 Image 類的 operator=() 的實現代碼,看起來合情合理:
Image &operator=(const Image &r)
{
delete p;
p = new Jpeg( *r.p );
return *p;
}
但,如果 r 跟調用對象是同一對象時,那將意味著在執行 delete p 之時就已經將 r 的圖像數據刪除了,此時再去根據此數據 new 一個新對象將會引發錯誤。糾正這個錯誤也不難,只要加個簡單的判斷:
Image &operator=(const Image &r)
{
if( *this == r ) // 自我檢測
return;
delete p;
p = new Jpeg( *r.p );
return *p;
}
這的確解決了所謂 “自我賦值安全性” 問題,但隨之而來還有另一個問題,那就是 “異常安全性” 問題,假設程序在分配堆內存時,不巧發生了始料未及的錯誤,也就是 new 語句發生了異常,此時因為 原先對象的圖像數據 p 已經被刪除,因此這個賦值運算將會導致一個尷尬的結局:新的數據尚未被正常賦予,舊的數據已經被匆匆刪除。
因此,我們還需要仔細打磨以上代碼,可以將之修改為:
Image &operator=(const Image &r)
{
Jpeg *tmp = this->p;
p = new Jpeg( *r.p );
delete tmp;
return *this;
}
此時,如果 new 語句再次發生異常,將不會對任何數據造成影響,可以免除編寫 自我檢測 代碼。當然,如果恰巧確實發生了 自我賦值 事件,那么代碼將會白白浪費時間創建了一個原圖像的復制品,然后讓指針指向新的復制品上。如果你很在乎這個事情,你可以將 自我檢測 代碼重新加到代碼中,可是這又將增加程序的尺寸,引入了一個新的結構分支,prefetching、caching 和 pipelining 指令的效率都會被拖累。因此你需要權衡這二者中的利弊。
總結:
編寫 operator=() 函數時要格外注意操作數是否是同一對象。
需要格外注意會發生異常(尤其是堆內存申請的代碼)的代碼處,是否會導致程序邏輯的不一致性。
保證任何函數在同時操作多個對象時,哪怕有多個對象是同一對象的情況下也能正常執行。
長按以下二維碼進入 微店●秘籍酷 獲取 IT 編程技術入門指導
微信原文:https://mp.weixin.qq.com/s?__biz=MzAxNzYzMTU0Ng==&mid=2651289247&idx=1&sn=8d50a35fb3b2fa57095ea8c3d0088afe&chksm=801146cab766cfdc52d2f2b4f6e76b59f92595263e13a67a3583bb03234aaea04f577ac50435#rd
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。