拷貝構造函數(深拷貝、淺拷貝)、賦值操作符的重載


1、拷貝構造函數的概述

  在C++中,下面三種對象需要調用拷貝構造函數(有時也稱“復制構造函數”):

 1) 一個對象作為函數參數,以值傳遞的方式傳入函數體;
 2) 一個對象作為函數返回值,以值傳遞的方式從函數返回;
 3) 一個對象用於給另外一個對象進行初始化(常稱為復制初始化);
 
  如果在前兩種情況不使用拷貝構造函數的時候,就會導致一個指針指向已經被刪除的內存空間(比如程序員面試寶典p95)。對於第三種情況來說,可以分為兩種情況:一是在聲明對象時直接用另一個對象對它初始化;二是先聲明一個對象,然后用另一個對象對它賦值(此時,需要重載賦值操作符)。
  
    通常的原則是:①對於凡是包含動態分配成員或包含指針成員的類都應該提供拷貝構造函數;②在提供拷貝構造函數的同時,還應該考慮重載"="賦值操作符號。
 
  拷貝構造函數是一種特殊的構造函數,它必須以引用的形式傳遞(參數為const &)。 如果在類中沒有顯式的聲明一個拷貝構造函數,那么,編譯器會自動生成一個來進行對象之間非static成員的位拷貝(Bitwise Copy),此時是淺拷貝

   2、淺拷貝和深拷貝

下面用一個例子來說明:
 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;
};

int main(int argc, char* argv[])
{
CExample theObjone;
theObjone.Init(40);
  CExample theObjtwo=theObjone;//在聲明theObjtwo時直接用對象theObjone對其初始化
 ...
}
 回顧一下此語句的具體過程:首先建立對象theObjtwo,並調用其構造函數,然后成員被復制初始化。其完成方式是內存拷貝,復制所有成員的值。完成后,theObjtwo.pBuffer==theObjone.pBuffer。即它們將指向同樣的地方,指針雖然復制了,但所指向的空間並沒有復制,而是由兩個對象共用了(即是淺拷貝)。這樣不符合要求,對象之間不獨立了,並為空間的刪除帶來隱患。所以需要采用必要的手段來避免此類情況:可以在構造函數中添加操作來解決指針成員的這種問題。
   所以C++語法中除了提供缺省形式的構造函數外,還規范了另一種特殊的構造函數:拷貝構造函數,一種特殊的構造函數重載。上面的語句中,如果類中定義了拷貝構造函數,在對象復制初始化時,調用的將是拷貝構造函數,而不是缺省構造函數。在拷貝構造函數中,可以根據傳入的變量,復制指針所指向的資源。(深拷貝)拷貝構造函數的格式為:類名(const 類名& 對象名);//拷貝構造函數的原型,參數是常量對象的引用。由於拷貝構造函數的目的是成員復制,不應修改原對象,所以建議使用const關鍵字。
 
 提供了拷貝構造函數后的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;
}
 總結:當某對象是按值傳遞時(無論是作為函數參數,還是作為函數返回值),編譯器都會先建立一個此對象的臨時拷貝,而在建立該臨時拷貝時就會調用類的拷貝構造函數

3、賦值操作符重載

  下面的代碼與上例相似
 
  int main(int argc, char* argv[])
 
  {
   CExample theObjone;
  theObjone.Init(40);
   CExample theObjthree;
  theObjthree.init(60);
  //現在需要一個對象賦值操作,被賦值對象的原內容被清除,並用右邊對象的內容填充。
 theObjthree=theObjone;
  return 0;
  }
 
  這里也用到了"="號,但與"復制初始化"中的例子並不同。"復制初始化"的例子中,"="在對象聲明語句中,表示初始化。更多時候,這種初始化也可用圓括號表示。例如:CExample theObjthree(theObjone);。而本例子中,"="表示賦值操作。將對象theObjone的內容復制到對象theObjthree,這其中涉及到對象theObjthree原有內容的丟棄,新內容的復制。 但"="的缺省操作只是將成員變量的值相應復制。由於對象內包含指針,將造成不良后果:指針的值被丟棄了,但指針指向的內容並未釋放。指針的值被復制了,但指針所指內容並未被復制。
 
  因此,包含動態分配成員的類除提供拷貝構造函數外,還應該考慮重載"="賦值操作符號。 類定義變為:
  class CExample
  {
public:
 CExample(){pBuffer=NULL; nSize=0;}
 ~CExample(){delete pBuffer;}
 CExample(const CExample&); //拷貝構造函數
CExample& operator = (const CExample&); //賦值符重載
 void Init(int n){ pBuffer=new char[n]; nSize=n;}
private:
char *pBuffer; //類的對象中包含指針,指向動態分配的內存資源
 int nSize;
  };
//賦值操作符重載
 CExample& CExample::operator = (const CExample& RightSides)
 {
 if (this == &RightSides) // 如果自己給自己賦值則直接返回
 {return *this;}
 nSize=RightSides.nSize;//復制常規成員
 char *temp=new char[nSize]; //復制指針指向的內容
 memcpy(temp,RightSides.pBuffer,nSize*sizeof(char));
 delete []pBuffer;//刪除原指針指向內容(將刪除操作放在后面,避免X=X特殊情況下,內容的丟失)
 pBuffer=temp;//建立新指向
return *this;
 }

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com