在Effective C++中看到這個問題時才發現以前寫的代碼完全沒有注意過這個問題
“自我賦值”發生在對象被賦值給對象本身時,例如:
class Weight
{...};
...
w=w;
雖然這看上去確實非常的蠢,但是誰也不能保證這不會發生,畢竟這是合法的,但是問題出來了,我們寫的顯示賦值函數一般是這樣的,假設我們在類中使用了動態分配。
class Bitmap{...};
class Weight
{
...
private:
Bitmap * _pb;
};
Weight & Weight::operatot=(const Weight & rhs)
{
delete _pb;
_pb=new Bitmap(rhs._pb);
return *this;
}
好的,現在面臨的問題是,*this和rhs這兩個指向了同一個對象,在執行delete _pb 時同時也將rhs的Bitmap銷燬了。
對此提出了兩種有效的解決方法
①傳統做法在operator=之前做一個“證同測試(identity test)”
Weight & Weight::operatot=(const Weight & rhs)
{
if(this == &rhs)
return *this;
delete _pb;
_pb=new Bitmap(rhs._pb);
return *this;
}
這樣做完全行得通,但是爲一件很少發生的事情花費額外的判斷操作似乎不是很划算。
②依靠精心周到的語句順序解決
調整最初的源代碼
Weight & Weight::operatot=(const Weight & rhs)
{
Bitmap *pOrig=_pb; //記住原先的_pb
_pb=new Bitmap(rhs._pb); //令_pb指向*_pb的一個副本
delete pOrig; //刪除原先的_pb
return *this;
}
再調整順序後,在重新new之後再摧毀原來的_pb,這樣的操作具有很高的異常安全性。這裏有一個問題,如果new失敗了怎麼辦?
我相信所有人一定都寫過這樣一段代碼
int *p=new int[5];
if(p==NULL)
return -1;
當我們使用malloc/calloc分配內存時,檢測返回值是否爲”空指針”是一個良好的習慣,可惜的是new在默認狀態下,分配失敗並不會返回一個空指針,而是拋出(throw)一個異常!!
對此正確的操作有如下兩個方法:
①捕獲異常
try
{
_pb=new Bitmap(rhs._pb);
... //其他操作
}
catch(const bad_alloc& e )
{
return -1;
}
②標準 C++ 亦提供了一個方法來抑制 new 拋出異常,而返回空指針
int* p = new (std::nothrow) int;
// 這樣如果 new 失敗了,就不會拋出異常,而是返回空指針
if ( p == 0 ) // 如此這般,這個判斷就有意義了
return -1;
... // 其它代碼
delete p;