c++程式設計習慣十(如何處理operator=中的自我賦值問題)
“自我賦值”發生在物件被賦值給自己時:
class Test{...};
Test t;
...
t=t;//賦值給自己
這看起來雖然有點蠢,但是是合法的,所以不要認定客戶不會那麼做。此外,賦值動作不是總是這樣一眼被看出來,例如:
a[i]=a[j];//潛在的自我賦值
再看:
*px=*py;
如果px和py恰好都指向同一個東西,這就是自我賦值。這些潛在的自我賦值緣起於別名,也就是指標和引用。一般來說,如果某段程式碼操作指標和引用被用來指向同一型別的物件,就需要考慮這些物件是否為同一個。實際上兩個物件只要來自同一個繼承體系,甚至不需要宣告為相同型別就可能造成“別名”,因為一個基類的引用或指標可以指向派生類物件。
class Base{...};
class Drived:public Base{...};
void dosomething{const Base& pb,Drived* pd);//pb和*pd有可能是同一物件
我麼可能會寫一個管理資源的類,可能會掉進“在停止使用資源之前意外釋放了它”的陷阱。假設建立一個class用來儲存一個指標指向一塊動態分配的點陣圖:
class Bitmap{...}; class Widget{ ... private: Bitmap* pb; //指標指向一個從heap分配的物件 }; Widget& Widget::operator=(const Widget& other) { delete pd; pb=new Bitmap(*other.pb); return *this; }
這裡的自我賦值問題是,operator=函式內的*this(賦值的目的端)和other有可能是同一個物件。如果是這樣的話,delete就連同other一起銷燬了,這就很尷尬了。
要避免這種錯誤,我們往往會這樣做:
Widget& Widget::operator=(const Widget& other)
{
if(this==&other) return *this;
delete pd;
pb=new Bitmap(*other.pb);
return *this;
}
這樣做是行的通的,檢查了自我賦值,如果是就什麼也不做,但是這裡仍然會存在一個異常方面的麻煩。明確的說,如果“new Bitmap”導致異常(不論是因為分配時記憶體不足或因為Bitmap的copy建構函式丟擲異常),Widget最終會持有一個指標指向一塊被刪除的Bitmap。這樣的指標是一個野指標,所以是不安全的,無法安全的刪除他們,甚至無法安全的讀取。
不過令人高興的是,讓operator=具備“異常安全性”往往自動獲得“自我賦值安全”的回報,因此我們更傾向於把焦點放在實現“異常安全性”上,這裡我們只需要注意在複製pb所指東西之前別刪除pb:
Widget& Widget::operator=(const Widget& other)
{
Bitmap* porig = pb;//記住原先的pb
pb=new Bitmap(*other.pb);//令pb指向*pb的一個附件
delete porig;//刪除原先的pb
return *this;
}
現在如果“new Bitmap”丟擲異常,pb保持原狀,這段程式碼是可以處理自我賦值的,因為有一個復件,雖然不是最高效的方法,但是行得通。
總結:
1、確保當物件自我賦值時operator=有良好行為。其中包括比較“來源物件”和“目標物件”的地址、精心周到的語句順序。
2、確保任何函式如果操作一個以上的物件,而其中多個物件是同一個物件時,其行為仍然正確。