位拷貝拷貝的是地址(也叫淺拷貝),而值拷貝則拷貝的是內容(深拷貝)。深拷貝和淺拷貝可以簡單理解為:如果一個類擁有資源,當這個類的對象發生復制過程的時候,資源重新分配,這個過程就是深拷貝,反之,沒有重新分配資源,就是淺拷貝。
位拷貝,及"bitwise assignment"是指將一個對象的內存映像按位原封不動的復制給另一個對象,所謂值拷貝就是指,將原對象的值復制一份給新對象。 在用"bitwise assignment"時會直接將對象的內存映像復制給另一個對象,這樣兩個對象會指向同一個內存區域,當一個對象被釋放後,另一個對象的指針會成為野指針(懸垂指針)。這時,就應該編寫operator=和copy constructor來實現值拷貝 。
默認的拷貝構造函數”和“缺省的賦值函數”均采用“位拷貝”而非“值拷貝”的方式來實現,倘若類中含有指針變量,這兩個函數注定將出錯。
當用一個已初始化過了的自定義類類型對象去初始化另一個新構造的對象的時候,拷貝構造函數就會被自動調用。也就是說,當類的對象需要拷貝時,拷貝構造函數將會被調用。以下情況都會調用拷貝構造函數:
如果在類中沒有顯式地聲明一個拷貝構造函數,那麼,編譯器將會自動生成一個默認的拷貝構造函數,該構造函數完成對象之間的位拷貝。位拷貝又稱淺拷貝。自定義拷貝構造函數是一種良好的編程風格,它可以阻止編譯器形成默認的拷貝構造函數,提高源碼效率。
如果沒有自定義復制構造函數,則系統會創建默認的復制構造函數,但系統創建的默認復制構造函數只會執行“位拷貝”,即將被拷貝對象的數據成員的值一一賦值給新創建的對象,若該類的數據成員中有指針成員,則會使得新的對象的指針所指向的地址與被拷貝對象的指針所指向的地址相同,delete該指針時則會導致兩次重復delete而出錯。下面拿這個經典示例:
Class String{
public:
String(const char *ch=NULL);//默認構造函數
String(const String &str);//拷貝構造函數
~String(void);
String &operator=(const String &str);//賦值函數
private:
char *m_data;
};
如果以String為例定義strA和strB
int main()
{
String strA("hello");
String strB("world");
strB = strA;
// 結果導致 strA 和 strB 的指針都指向了同一個地址
// 函數結束析構時
// 同一個地址被delete兩次
return 0;
}
如果不主動編寫拷貝構造函數和賦值函數,編譯器將以“位拷貝”的方式自動生成缺省的函數。倘若類中含有指針變量,那麼這兩個缺省的函數就隱含了錯誤。以類String的兩個對象strA,strB為例,假設strA.m_data的內容為“hello”,strB.m_data的內容為“world”。 現將strA賦給strB,缺省賦值函數的“位拷貝”意味著執行strB.m_data =strA.m_data。這將造成三個錯誤:
對於編譯器,如果不主動編寫拷貝函數和賦值函數,它會以位拷貝的方式自動生成缺省的函數。