位拷貝拷貝的是地址(也叫淺拷貝),而值拷貝則拷貝的是內容(深拷貝)。深拷貝和淺拷貝可以簡單理解為:如果一個類擁有資源,當這個類的對象發生複製過程的時候,資源重新分配,這個過程就是深拷貝,反之,沒有重新分配資源,就是淺拷貝。
位拷貝,及"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。這將造成三個錯誤:
- strB.m_data 原有的記憶體沒被釋放,造成記憶體泄露;
- strB.m_data和strA.m_data指向同一塊記憶體,strA或strB任何一方變動都會影響另一方;
對於編譯器,如果不主動編寫拷貝函數和賦值函數,它會以位拷貝的方式自動產生預設的函數。