一、我們平時習慣寫的指派操作代碼
我們平時可能已經習慣了這樣的代碼,并且認為是對的:
#include<iostream>
using namespace std;
class Demo{
public:
Demo& operator=(const Demo& x);
Demo(int x=10,int y=15):a(x),b(y){
p=new int;
}
void Print(){
cout<<"a="<<a<<endl;
cout<<"b="<<b<<endl;
}
private:
int a;
int b;
int* p;
};
Demo& Demo::operator=(const Demo& x){
a=x.a;
b=x.b;
delete p;
p=new int(*(x.p));
return *this;
}
int main(){
Demo obj(40,50);
Demo obj2;
obj2=obj;
obj.Print ();
obj2.Print();
return 0;
}
二、存在的問題
确實,以上代碼運作能通過,指派操作也能完美運作。但是,隻是表面上看起來合理而已,當出現自我指派時并不安全(實際上,也不具備異常安全性)。上面代碼中,有可能*this和x是同一對象,假如真的如此,delete不僅僅删除了目前對象的Demo,也删除了x的Demo,這樣,這函數末尾,持有的指針會指向一個已經删除的對象,(注:但是用obj=obj時,也能運作通過,不知道為什麼??,先按書中的來吧)
三、解決方法
為了解決這種錯誤,我們需要這樣做:
Demo& Demo::operator=(const Demo& x){
if(this==&x)//從位址上判斷是否指向同一塊記憶體
return *this;
a=x.a;
b=x.b;
delete p;
p=new int(*(x.p));
return *this;
}
上述方法确實能解決自我指派問題,但是還不具備異常安全性,假設p=new int(*(x.p))出現異常,那麼最終會導緻Demo持有一個指針指向被删除的一塊Int,為了解決異常問題,可以采取下面的政策:
Demo& Demo::operator=(const Demo& x){
if(this==&x)
return *this;//可以省略這段防止自我指派的代碼
a=x.a;
b=x.b;
int* temp=p;//儲存舊指針
p=new int(*(x.p));//如果new操作發生異常,則仍然指向原來的空間
delete temp;//删除原先的temp
return *this;
}
注意上述代碼中即使沒有
if(this==&x)
return *this;
也是行的通的,因為我們對原來的指針做了一個備份,删除舊的,指向新的;
四、最優方法
解決自我指派和異常問題還有兩個版本,即使用所謂的copy and swap技術
Demo& operator= (const Demo& x)
{
Demo temp(x);
swap(*this, temp);
return *this;
}
copy and swap的原理:為你打算修改的對象做出一份副本,然後在那份副本身上做出一切修改,若有任何修改動作抛出異常,原對象仍然保持未修改的狀态;待所有改變都成功後,再将修改過的那一個副本和原對象在一個抛出異常給的操作中置換(swap)(具體參見條款29);這裡把所有負擔都落在了複制構造函數上,如果出現異常,那麼swap就不會執行;
另一個版本是
Demo& operator= (const Demo x)
{
swap(*this, temp);
return *this;
}
注意,這個版本和上面的有所不同,參數是通過傳值,而傳值操作會生成一份複件,即此時的x是原對象的一份副本(會調用指派構造函數)(傳值方式請參考條款20),那麼申請臨時變量的任務就落在形參上了;這樣的方式犧牲了代碼的清晰性,而增加了代碼的高效;
參考:Effective C++ 3rd(侯捷譯)