拷貝建構函式

linlinlinxi007發表於2010-02-02

拷貝建構函式,是一種特殊的建構函式,它由編譯器呼叫來完成一些基於同一類的其他物件的構建及初始化。其唯一的引數(物件的引用)是不可變的(const型別)。此函式經常用在函式呼叫時使用者定義型別的值傳遞及返回。拷貝建構函式要呼叫基類的拷貝建構函式和成員函式。如果可以的話,它將用常量方式呼叫,另外,也可以用非常量方式呼叫。

  在C++中,下面三種物件需要呼叫拷貝建構函式:

  1) 一個物件以值傳遞的方式傳入函式體;

  2) 一個物件以值傳遞的方式從函式返回;

  3) 一個物件需要通過另外一個物件進行初始化;

  如果在前兩種情況不使用拷貝建構函式的時候,就會導致一個指標指向已經被刪除的記憶體空間。對於第三種情況來說,初始化和賦值的不同含義是建構函式呼叫的原因。事實上,拷貝建構函式是由普通建構函式和賦值操作符共同實現的。描述拷貝建構函式和賦值運算子的異同的參考資料有很多。

  拷貝建構函式不可以改變它所引用的物件,其原因如下:當一個物件以傳遞值的方式傳一個函式的時候,拷貝建構函式自動的被呼叫來生成函式中的物件。如果一個物件是被傳入自己的拷貝建構函式,它的拷貝建構函式將會被呼叫來拷貝這個物件這樣複製才可以傳入它自己的拷貝建構函式,這會導致無限迴圈直至棧溢位(Stack Overflow)。除了當物件傳入函式的時候被隱式呼叫以外,拷貝建構函式在物件被函式返回的時候也同樣的被呼叫。

  如果在類中沒有顯式的宣告一個拷貝建構函式,那麼,編譯器會自動生成一個來進行物件之間的位拷貝(Bitwise Copy)。這個隱含的拷貝建構函式簡單的關聯了所有的類成員。注意到這個隱式的拷貝建構函式和顯式宣告的拷貝建構函式的不同在於對成員的關聯方式。顯式宣告的拷貝建構函式關聯的只是被例項化的類成員的預設建構函式,除非另外一個建構函式在類初始化或構造列表的時候被呼叫。

  拷貝建構函式使程式更有效率,因為它不用再構造一個物件的時候改變建構函式的引數列表。設計拷貝建構函式是一個良好的風格,即使是編譯系統會自動為你生成預設拷貝建構函式。事實上,預設拷貝建構函式可以應付許多情況。

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

  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.Init(40);

  //現在需要另一個物件,需要將他初始化稱物件一的狀態

  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=NULL;

  pBuffer=temp; //建立新指向

  return *this

  }

  三、注意事項

  常見錯誤:

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

  CExample::CExample(const CExample& RightSides)

  {

  *this=RightSides //呼叫過載後的"="

  }

  而這樣會造成迴圈呼叫過載後的"="和拷貝建構函式,最後造成棧溢位(Stack Overflow)。

  四,拷貝建構函式與建構函式的區別

  class 類名

  {

  public:

  類名(形參引數)//建構函式

  類名(類名&物件名)//拷貝建構函式

  ,,,,,,,,,,,,,,,,,,,,,

  };

  拷貝建構函式的實現:

  類名::類名(類名&物件名)//拷貝建構函式的實現

  {函式體}

  不完整的例子

  拷貝建構函式:

  Class Point

  {

  Public:

  Point(int xx=0,int yy=m)(X=xx;Y=yy;)

  Point(Point& p);

  Int getX() {return X;}

  Int getY(){ return Y;}

  Private :

  Int X,Y;

  }

  Point::Point(Point& p)

  {

  X=p.X;

  Y=p.Y;

  Cout<<"拷貝建構函式吊樣"<<endl;

  }

相關文章