C++/CLI思辨錄之拷貝建構函式

linlinlinxi007發表於2010-02-02

雖然物件複製看上去很簡單,然而如果你沒有對其正確理解,可能會出現一些嚴重問題。預設情況下,複製物件會導致相應的所有成員的複製。如果你只有例項成員,這看上去是相當不錯的。但是如果你的類中含有指向在堆中分配的物件時,情況會怎樣呢?考慮下面的程式碼片斷:

#include <stdio.h>
#include <string.h>
class Person
{
 private:
  char* _name;
 public:
  Person()
  {
   _name = new char[256];
  }
  void SetName(const char* name)
  {
   if(strlen(name) + 1 < 256)
    strcpy(_name,name);
  }
  void PrintName()
  {
   printf("%s/n",_name);
  }
 };
 int main()
 {
  // 建立物件的第一個例項並賦於名字為John
  Person p1;
  p1.SetName("John");
  p1.PrintName();
  //通過複製p1引用的物件建立另一個物件
  Person p2(p1);
  p2.SetName("Alice");
  p2.PrintName();
  //現在再輸出p1的名字
  p1.PrintName();
  scanf("q");
  return 0;
 }
  這裡的類Person有一個指向在堆上分配的字元陣列的指標。當構造Person物件時,它建立該字元陣列並把它的位置存放到變數_name中。

  但是當你建立Person 物件 p2 時,p2的成員用p1的成員初始化。因而,p1的 _name與p2的 _name指向相同的堆物件。如在上例中看到的,呼叫p2.SetName將改變由這兩個類共享的值。所以,當第二次呼叫p1.PrintName,列印結果是"Alice"。

  所以,這不是我們複製物件所期望的結果,而且還會導致堆崩潰的問題。請再考慮某個函式刪除了該陣列而p1又要呼叫該函式的情況?下面,當p2呼叫PrintName時,它將盡量存取實際上不是在堆上的物件。這種情況下產生的結果往往是難以預料的。

  C++允許我們通過定義拷貝建構函式來克服這類問題。在我們每次通過複製另一個物件來初始化一個物件時,拷貝建構函式都被執行。你可以在拷貝建構函式中覆蓋掉預設的成員函式的複製行為。

  所以,我們的類Person應該修改如下:

class Person
{
 private:
  char* _name;
 public:
  Person()
  {
   _name = new char[256];
  }
  // 這是拷貝建構函式。在此我們初始化一個新的陣列,為Person的例項所用
  Person(Person&)
  {
   _name = new char[256];
  }
  void SetName(const char* name)
  {
   if(strlen(name) + 1 < 256)
    strcpy(_name,name);
  }
  void PrintName()
  {
   printf("%s/n",_name);
  }
};
  這裡類Person中的拷貝建構函式保證了它初始化一個新的陣列,為在複製時產生的每一個物件例項所用。這就避免了前面我們提到的問題。

相關文章