1. 程式人生 > >關於拷貝建構函式和賦值運算子,轉自VC知識庫

關於拷貝建構函式和賦值運算子,轉自VC知識庫

(源出處:http://www.vckbase.com/document/viewdoc/?id=788)

關於拷貝建構函式和賦值運算子
作者:馮明德


重點:包含動態分配成員的類 應提供拷貝建構函式,並重載"="賦值操作符。

以下討論中將用到的例子:

class CExample
{
public:
	CExample(){pBuffer=NULL; nSize=0;}
	~CExample(){delete pBuffer;}
	void Init(int n){ pBuffer=new char[n]; nSize=n;}
private:
	char *pBuffer; //類的物件中包含指標,指向動態分配的記憶體資源
	int nSize;
};

這個類的主要特點是包含指向其他資源的指標。

pBuffer指向堆中分配的一段記憶體空間。

一、拷貝建構函式

int main(int argc, char* argv[])
{
	CExample theObjone;
	theObjone.Init40);
	
	//現在需要另一個物件,需要將他初始化稱物件一的狀態
	CExample theObjtwo=theObjone;
	...
}

語句"CExample theObjtwo=theObjone;"用theObjone初始化theObjtwo。

其完成方式是記憶體拷貝,複製所有成員的值。

完成後,theObjtwo.pBuffer==theObjone.pBuffer。

即它們將指向同樣的地方,指標雖然複製了,但所指向的空間並沒有複製,而是由兩個物件共用了。這樣不符合要求,物件之間不獨立了,併為空間的刪除帶來隱患。

所以需要採用必要的手段來避免此類情況。

回顧以下此語句的具體過程:首先建立物件theObjtwo,並呼叫其建構函式,然後成員被拷貝。

可以在建構函式中新增操作來解決指標成員的問題。

所以C++語法中除了提供預設形式的建構函式外,還規範了另一種特殊的建構函式:拷貝建構函式,上面的語句中,如果類中定義了拷貝建構函式,這物件建立時,呼叫的將是拷貝建構函式,在拷貝建構函式中,可以根據傳入的變數,複製指標所指向的資源。

拷貝建構函式的格式為:建構函式名(物件的引用)

提供了拷貝建構函式後的CExample類定義為:

class CExample
{
public:
	CExample(){pBuffer=NULL; nSize=0;}
	~CExample(){delete pBuffer;}
	CExample(const CExample&); //拷貝建構函式
	void Init(int n){ pBuffer=new char[n]; nSize=n;}
private:
	char *pBuffer; //類的物件中包含指標,指向動態分配的記憶體資源
	int nSize;
};

CExample::CExample(const CExample& RightSides) //拷貝建構函式的定義
{
	nSize=RightSides.nSize; //複製常規成員
	pBuffer=new char[nSize]; //複製指標指向的內容
	memcpy(pBuffer,RightSides.pBuffer,nSize*sizeof(char));
}

這樣,定義新物件,並用已有物件初始化新物件時,CExample(const CExample& RightSides)將被呼叫,而已有物件用別名RightSides傳給建構函式,以用來作複製。

原則上,應該為所有包含動態分配成員的類都提供拷貝建構函式。

拷貝建構函式的另一種呼叫。

當物件直接作為引數傳給函式時,函式將建立物件的臨時拷貝,這個拷貝過程也將調同拷貝建構函式。

例如

BOOL testfunc(CExample obj);

testfunc(theObjone); //物件直接作為引數。

BOOL testfunc(CExample obj)
{
	//針對obj的操作實際上是針對複製後的臨時拷貝進行的
}

還有一種情況,也是與臨時物件有關的

當函式中的區域性物件被被返回給函式調者時,也將建立此區域性物件的一個臨時拷貝,拷貝建構函式也將被呼叫

CTest func()
{
	CTest theTest;
	return theTest
}

二、賦值符的過載

下面的程式碼與上例相似

int main(int argc, char* argv[])
{
	CExample theObjone;
	theObjone.Init(40);
	
	CExample theObjthree;
	theObjthree.Init(60);

	//現在需要一個物件賦值操作,被賦值物件的原內容被清除,並用右邊物件的內容填充。
	theObjthree=theObjone;
	return 0;
}

也用到了"="號,但與"一、"中的例子並不同,"一、"的例子中,"="在物件宣告語句中,表示初始化。更多時候,這種初始化也可用括號表示。

例如 CExample theObjone(theObjtwo);

而本例子中,"="表示賦值操作。將物件theObjone的內容複製到物件theObjthree;,這其中涉及到物件theObjthree原有內容的丟棄,新內容的複製。

但"="的預設操作只是將成員變數的值相應複製。舊的值被自然丟棄。

由於物件內包含指標,將造成不良後果:指標的值被丟棄了,但指標指向的內容並未釋放。指標的值被複制了,但指標所指內容並未複製。

因此,包含動態分配成員的類除提供拷貝建構函式外,還應該考慮過載"="賦值操作符號。

類定義變為:

class CExample
{
	...
	CExample(const CExample&); //拷貝建構函式
	CExample& operator = (const CExample&); //賦值符過載
	...
};

//賦值操作符過載
CExample & CExample::operator = (const CExample& RightSides)
{
	nSize=RightSides.nSize; //複製常規成員
	char *temp=new char[nSize]; //複製指標指向的內容 
	memcpy(temp,RightSides.pBuffer,nSize*sizeof(char));

	delete []pBuffer; //刪除原指標指向內容  (將刪除操作放在後面,避免X=X特殊情況下,內容的丟失)
	pBuffer=temp;   //建立新指向
	return *this
}

三、拷貝建構函式使用賦值運算子過載的程式碼。

CExample::CExample(const CExample& RightSides)
{
	pBuffer=NULL;
	*this=RightSides	 //呼叫過載後的"="
}