在對象拷貝過程中,如果沒有自定義拷貝構造函數,系統會提供一個缺省的拷貝構造函數,缺省的拷貝構造函數對於基本類型的成員變量,按字節複製,對於類類型成員變量,調用其相應類型的拷貝構造函數。
閱讀《高質量的c c++編程》,第9章有這樣一段話,類似的話在《c++primer》《effective C++》都有所提及,那就是拷貝構造函數問題,這個是類編寫者的一個基礎問題。
位拷貝(淺拷貝)舉例,a指向b,b的改變其實會影響a的改變,同時a原本指向的空間發生泄漏。
然後這種情況下有了深拷貝。
我對其繪製思維導圖,方便閱讀並用分點的方式進行總結;
何時調用?
以下情況都會調用拷貝構造函數:
一個對象以值傳遞的方式傳入函數體
一個對象以值傳遞的方式從函數返回
一個對象需要通過另外一個對象進行初始化。
淺拷貝:位拷貝,拷貝構造函數,賦值重載
多個對象共用同一塊資源,同一塊資源釋放多次,崩潰或者內存泄漏
深拷貝:每個對象共同擁有自己的資源,必須顯式提供拷貝構造函數和賦值運算符。
缺省拷貝構造函數在拷貝過程中是按字節複製的,對於指針型成員變量只複製指針本身,而不復制指針所指向的目標--淺拷貝。
我們用自己編寫的string舉例
-
- class String
- {
-
- public:
- const char* c_str()
- {
- return _str;
- }
-
- String(const char* str = "")
- :_str(new char[strlen(str) + 1])
- {
- strcpy(_str, str);
- }
- String(const String &s)
- :_str(NULL)
- {
- String tmp(s._str);
- swap(_str, tmp._str);
- }
- ~String()
- {
- if (_str)
- {
- delete[]_str;
- }
- }
-
- private:
- char* _str;
-
- };
通過開闢空間的方式,進行深拷貝
- String s1("字符串1");
- String s2(s1);
- cout << s2.c_str() << endl;
拷貝成功;
這種方式採取的 拷貝構造,注意這個
- String(const String &s)
- :_str(NULL)
- {
- String tmp(s._str);
- swap(_str, tmp._str);
- }
代碼解析:其中this指向拷貝的對象,s指向試圖拷貝的原對象。(測試中的 this指向s2,s指向s1)
其中利用構造函數開闢空間,建立臨時的tmp,然後進行交換完成拷貝。
當然,我們也可以使用賦值操作符重載完成這一功能(如例子s1=s2)
- String& operator =(const String& s)
- {
- if (this != &s)
- {
- String tmp(s._str);
- swap(tmp._str, _str);
- return *this;
- }
- }//調用構造析構
- //本代碼是tmp調用的構造函數
- String(const char* str = "")
- :_str(new char[strlen(str) + 1])
- {
- strcpy(_str, str);
- }
- /*String tmp(s._str)
- 調用這個構造函數,開闢空間,建立一個和s1一樣大小的空間,並拷貝值
- */
代碼解析:
s1(this),s2(s)
建立tmp,tmp有和s2一樣大的空間,一樣的數值(調用構造函數),然後交換使s1(this)指向2號空間,獲得拷貝,tmp指向3號空間,tmp生命週期結束調用析構函數釋放,功能完成。
當然 賦值重載函數可以寫的更加簡潔
- String &operator=(String s)
- {
- swap(_str, s._str);
- return *this;
- }
利用tmp的方式是 藉助構造函數,這一種方式則是藉助拷貝構造函數