關於拷貝構造函數和賦值運算符
作者:馮明德
重點:包含動態分配成員的類 應提供拷貝構造函數,並重載"="賦值操作符。
以下討論中將用到的例子:
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 //調用重載後的"=" }