1. 程式人生 > >c++程式設計習慣十(如何處理operator=中的自我賦值問題)

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、確保任何函式如果操作一個以上的物件,而其中多個物件是同一個物件時,其行為仍然正確。