淺拷貝復制指針本身而非指向內容,導致多個對象共享同一內存,析構時引發重復釋放或野指針;深拷貝則復制指針指向的數據,各自獨立。1. 默認拷貝構造函數執行淺拷貝,適用于基本類型但不適用于指針。2. 實現深拷貝需手動編寫拷貝構造函數,逐個復制指針成員指向的數據。3. 若類含多個指針,均需深拷貝并注意異常安全。4. 拷貝構造函數、析構函數和賦值運算符需配套實現,遵循“三/五法則”。5. 現代c++++推薦使用智能指針或標準庫容器替代原始指針以自動管理資源。6. 實現后應測試對象拷貝、賦值與銷毀順序,并用工具檢測內存問題。
在c++中,拷貝構造函數是對象初始化的重要機制之一。很多人寫的時候只是簡單復制成員變量,結果一運行就出錯,尤其是涉及指針或動態內存時,問題更明顯。關鍵在于搞清楚深拷貝和淺拷貝的區別,并正確實現拷貝構造函數的邏輯。
什么是淺拷貝?為什么它會出問題?
默認的拷貝構造函數執行的是淺拷貝(shallow copy),也就是按字節復制每個成員變量的值。
如果你的類里有普通數據類型(如int、Float等),這沒問題。但一旦有指針或者動態分配的資源,就會出大問題。
立即學習“C++免費學習筆記(深入)”;
比如:
class MyClass { public: int* data; MyClass(int value) { data = new int(value); } };
這時候如果用默認的拷貝構造函數創建一個新對象:
MyClass obj1(10); MyClass obj2 = obj1; // 默認拷貝構造函數
那么obj1.data和obj2.data指向的是同一個地址。當其中一個對象析構時釋放了這塊內存,另一個對象再去訪問這個指針就成了“野指針”,程序極可能崩潰。
所以:
- 淺拷貝只復制指針本身,不復制指針指向的內容
- 多個對象共享同一塊內存,容易引發資源重復釋放或訪問非法內存
如何實現深拷貝?
深拷貝(deep copy)是指在拷貝對象時,把指針指向的數據也一起復制一份新的,這樣兩個對象互不影響。
要自己實現拷貝構造函數才能做到這一點。例如上面的例子可以這樣改:
MyClass(const MyClass& other) { data = new int(*other.data); // 拷貝指針指向的內容 }
這樣data指向的是一個新的int,值雖然一樣,但地址不同。兩個對象各自擁有自己的內存,就不會互相干擾了。
需要注意的幾個點:
- 如果類中有多個指針成員,都要一一深拷貝
- 動態數組要逐個復制,不能直接賦值指針
- 要考慮異常安全問題,new失敗時要處理好
析構函數與拷貝構造函數要配套寫
如果你自己寫了拷貝構造函數,大概率也要重寫析構函數和賦值運算符(=),這就是所謂的“三/五法則”。
因為:
- 如果你沒有正確的析構函數,可能導致內存泄漏
- 如果沒有自定義的賦值操作符,在賦值時又會使用默認的淺拷貝
舉個例子,完整的做法應該是這樣的:
class MyClass { public: int* data; MyClass(int value) { data = new int(value); } // 拷貝構造函數 MyClass(const MyClass& other) { data = new int(*other.data); } // 析構函數 ~MyClass() { delete data; } // 賦值運算符 MyClass& operator=(const MyClass& other) { if (this != &other) { int* newData = new int(*other.data); delete data; data = newData; } return *this; } };
常見誤區與建議
-
誤以為只要寫了拷貝構造函數就能避免問題
- 忘記寫析構函數或賦值操作符,導致行為不一致
- 不做深拷貝,還是照搬指針,等于白寫
-
沒考慮資源管理方式的變化趨勢
-
測試不到位
- 寫完拷貝構造函數后,一定要測試對象之間的拷貝、賦值、銷毀順序,看是否出現崩潰或內存泄露
- 可以用Valgrind之類的工具檢查內存問題
基本上就這些。實現拷貝構造函數看起來不復雜,但稍不注意就會踩坑,特別是在資源管理方面。深拷貝和淺拷貝的問題看似基礎,卻常常出現在實際項目中,尤其值得重視。