1. 程式人生 > >【C++】賦值運算子函式

【C++】賦值運算子函式

將已有的物件拷貝給另一個物件時,會用到賦值運算子函式, 尤其是當物件的記憶體分配在堆上時

函式原型:

ClassName & operator = (const ClassName &obj)

<span style="font-family:Microsoft YaHei;">#include <iostream>
using namespace std;
class CMyString {
public:
	CMyString(char* pData = NULL) ;
	CMyString(const CMyString &str);
	~CMyString();
private:
	char* m_data;
};</span>

為上面的類新增賦值運算子函式

CMyString & operator = (const CMyString &other) ;

注意

1. 是否把返回值的型別宣告為類的引用並在函式結束前返回例項自身的引用(即*this)。只有返回一個引用,才可以允許連續賦值。否則,如果函式的返回值是void,那麼這個賦值運算子不能連續賦值,假設有3個CMyString物件:str1, str2, str3, 在程式語句中str1 = str2 =str3將不能通過編譯。

2. 是否把傳入的引數型別宣告為常量引用。如果傳入的引數不是引用而是例項,那麼從形參到實參會呼叫一次拷貝建構函式。把引數宣告為引用可以避免這樣無謂的消耗,提高程式碼的效率。同時,在賦值運算子內不會改變傳入的例項的狀態,因此應該為傳入的引用引數加上const關鍵字。

3. 是否釋放例項自身已有的記憶體。如果我們忘記在分配新記憶體之前釋放自身已有的空間,程式將出現記憶體洩露。

4. 是否判斷傳入的引數和當前的例項(*this)是不是同一個例項。如果是同一個,就不進行賦值操作,直接返回。如果實現不判斷就賦值,那麼在釋放例項自身的記憶體時會導致嚴重的問題:當*this和傳入的引數是同一個例項時,一旦釋放了例項自身的記憶體,傳入的引數的記憶體也同時被釋放了,因此再也找不到需要賦值的內容了。

考慮這4點,我們應該可以寫出下面的程式碼:

CMyString& CMyString::operator = (const CMyString &other) {
	if (this == &other) {
		return *this;
	}
	else {
		delete []m_data;
		m_data = NULL;
		m_data = new char[strlen(other.m_data) + 1];
		strcpy(m_data, other.m_data);
		return *this;		
	}
}
考慮異常安全性呢?

在上面的函式中,我們在分配記憶體之前先用delete釋放了例項m_data的記憶體。如果此時記憶體不足導致new  char丟擲異常,m_data將會是一個空指標,這樣非常容易導致程式崩潰。也就是說,一旦在賦值運算子函式內部跑出一個異常,CMyString的例項就不再保有有效狀態,這違背了異常安全性原則。

要想在賦值運算子內部實現異常安全性,有兩種方法。

一個簡單的辦法時先用new分配新的內容,再用delete釋放已有的內容。這樣只在分配內容成功之後才釋放原來的內容,也就是當分配記憶體失敗時我們能確保CMyString的例項不會被修改。

還有個更好的辦法是,先建立一個臨時例項,再交換臨時例項和原來的例項。

CMyString& CMyString::operator = (const CMyString &other) {
	if (this == &other) {
		return *this;
	}
	else {
		CMyString strTemp(other);
		char* pTemp = strTemp.m_data;
		strTemp.m_data = m_data;
		m_data = pTemp;
		return *this;
	}
}

在這個函式中,我們先建立了一個臨時例項strTemp,接著把strTemp.m_data和例項自身的m_data互換。由於strTemp是一個區域性變數,當程式執行到if的外面時也就出了該變數的作用域,就會自動呼叫strTemp的解構函式,把strTemp.m_data所指向的記憶體釋放掉。由於strTemp.m_data指向的記憶體就是例項之前m_data的記憶體,這就相當於自動呼叫解構函式釋放例項的記憶體。

在新的程式碼中,我們在CMyString的建構函式裡用new分配記憶體,如果由於記憶體不足丟擲了異常,我們還沒有修改原來例項的狀態,因此例項的狀態還是有效的,也就保證了異常安全性。