掌握C++:揭秘寫時(shí)拷貝與淺深拷貝之間的關(guān)系
1. 經(jīng)典的string類問題
上一篇博客已經(jīng)對(duì)string類進(jìn)行了簡(jiǎn)單的介紹,大家只要能夠正常使用即可。
在面試中,面試官總喜歡讓學(xué)生自己來(lái)模擬實(shí)現(xiàn)string類,最主要是實(shí)現(xiàn)string類的構(gòu)造、拷貝構(gòu)造、賦值運(yùn)算符重載以及析構(gòu)函數(shù)。大家看下以下string類的實(shí)現(xiàn)是否有問題?
// 為了和標(biāo)準(zhǔn)庫(kù)區(qū)分,此處使用String class String { public: //String() // :_str(new char[1]) //{ // *_str = '\0'; //} //String(const char* str = "\0") // 錯(cuò)誤示范 //String(const char* str = nullptr) // 錯(cuò)誤示范 String(const char* str = "") { // 構(gòu)造String類對(duì)象時(shí),如果傳遞nullptr指針,可以認(rèn)為程序非法 if (nullptr == str) { assert(false); return; } _str = new char[strlen(str) + 1]; strcpy(_str, str); } ~String() { if (_str) { delete[] _str; _str = nullptr; } } private: char* _str; }; // 測(cè)試 void TestString() { String s1("hello string"); String s2(s1); }
說(shuō)明:上述String類沒有顯式定義其拷貝構(gòu)造函數(shù)與賦值運(yùn)算符重載,此時(shí)編譯器會(huì)合成默認(rèn)的,當(dāng)用s1構(gòu)造s2時(shí),編譯器會(huì)調(diào)用默認(rèn)的拷貝構(gòu)造。最終導(dǎo)致的問題時(shí),s1、s2共用同一塊內(nèi)存空間,在釋放時(shí)同一塊空間被釋放多次而引起程序崩潰,這種拷貝方式,稱為淺拷貝。
2. 淺拷貝
淺拷貝:也稱位拷貝,編譯器只是將對(duì)象中的值拷貝過(guò)來(lái)。如果對(duì)象中管理資源,最后就會(huì)導(dǎo)致多個(gè)對(duì)象共享同一份資源,當(dāng)一個(gè)對(duì)象銷毀時(shí)就會(huì)將該資源釋放掉,而此時(shí)另一些對(duì)象不知道該資源已經(jīng)被釋放,以為還有效,所以當(dāng)繼續(xù)對(duì)資源進(jìn)行操作時(shí),就會(huì)發(fā)生訪問違規(guī)。
就像一個(gè)家庭中有兩個(gè)孩子,但父母只買了一份玩具,兩個(gè)孩子愿意一塊玩,則萬(wàn)事大吉,萬(wàn)一不想分享就你爭(zhēng)我奪,玩具損壞。
可以采用深拷貝解決淺拷貝問題,即:每個(gè)對(duì)象都有一份獨(dú)立的資源,不要和其他對(duì)象共享。父母給每個(gè)孩子都買一份玩具,各自玩各自的就不會(huì)有問題了。
3. 深拷貝
如果一個(gè)類中涉及到資源的管理,其拷貝構(gòu)造函數(shù),賦值運(yùn)算符重載以及析構(gòu)函數(shù)必須要顯式給出。一般情況都是按照深拷貝方式提供。
3.1 傳統(tǒng)寫法的String類
class String { public: String(const char* str = "") { // 構(gòu)造String類對(duì)象時(shí),如果傳遞nullptr指針,可以認(rèn)為程序非法 if (nullptr == str) { assert(false); return; } _str = new char[strlen(str) + 1]; strcpy(_str, str); } String(const String& s) : _str(new char[strlen(s._str) + 1]) { strcpy(_str, s._str); } String& operator=(const String& s) { if (this != &s) { char* pStr = new char[strlen(s._str) + 1]; strcpy(pStr, s._str); delete[] _str; _str = pStr; } return *this; } ~String() { if (_str) { delete[] _str; _str = nullptr; } } private: char* _str; };
3.2 現(xiàn)代寫法的String類
class String { public: String(const char* str = "") { if (nullptr == str) { assert(false); return; } _str = new char[strlen(str) + 1]; strcpy(_str, str); } String(const String& s) : _str(nullptr) { String strTmp(s._str); swap(_str, strTmp._str); } // 對(duì)比下和上面的賦值那個(gè)實(shí)現(xiàn)比較好? String& operator=(String s) { swap(_str, s._str); return *this; } //String& operator=(const String& s) //{ // if (this != &s) // { // String strTmp(s); // swap(_str, strTmp._str); // } // return *this; //} ~String() { if (_str) { delete[] _str; _str = nullptr; } } private: char* _str; };
傳統(tǒng)寫法就是老老實(shí)實(shí)自己開空間、自己拷貝數(shù)據(jù)、自己刪除舊空間;而現(xiàn)代寫法則利用了swap()函數(shù)以及局部變量出作用域自動(dòng)銷毀的特性,讓函數(shù)和編譯器幫我們“打工”,我們只要坐享其成即可。這兩種方式在效率上并沒有什么區(qū)別,只是讓代碼看起來(lái)更簡(jiǎn)潔,但這又會(huì)使代碼的可讀性降低??傮w來(lái)說(shuō),我還是更偏向于傳統(tǒng)寫法。
4. 寫時(shí)拷貝
寫時(shí)拷貝就是一種拖延癥,是在淺拷貝的基礎(chǔ)之上增加了引用計(jì)數(shù)的方式來(lái)實(shí)現(xiàn)的。
引用計(jì)數(shù):用來(lái)記錄資源使用者的個(gè)數(shù)。在構(gòu)造時(shí),將資源的計(jì)數(shù)給成1,每增加一個(gè)對(duì)象使用該資源,就給計(jì)數(shù)增加1,當(dāng)某個(gè)對(duì)象被銷毀時(shí),先給該計(jì)數(shù)-1,然后再檢查是否需要釋放資源,如果計(jì)數(shù)為1,說(shuō)明該對(duì)象是資源的最后一個(gè)使用者,將該資源釋放;否則就不能釋放,因?yàn)檫€有其他對(duì)象也在使用該資源。
到此這篇關(guān)于掌握C++:揭秘寫時(shí)拷貝與淺深拷貝之間的關(guān)系的文章就介紹到這了,更多相關(guān)C++ 淺拷貝 深拷貝 寫時(shí)拷貝內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++哈希應(yīng)用之位圖,哈希切分與布隆過(guò)濾器詳解
這篇文章主要為大家詳細(xì)介紹了C++哈希應(yīng)用中的位圖、哈希切分與布隆過(guò)濾器,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,需要的可以參考一下2023-04-04C語(yǔ)言關(guān)于自定義數(shù)據(jù)類型之枚舉和聯(lián)合體詳解
枚舉顧名思義就是把所有的可能性列舉出來(lái),像一個(gè)星期分為七天我們就可以使用枚舉,聯(lián)合體是由關(guān)鍵字union和標(biāo)簽定義的,和枚舉是一樣的定義方式,不一樣的是,一個(gè)聯(lián)合體只有一塊內(nèi)存空間,什么意思呢,就相當(dāng)于只開辟最大的變量的內(nèi)存,其他的變量都在那個(gè)變量占據(jù)空間2021-11-11詳解C++ 共享數(shù)據(jù)保護(hù)機(jī)制
這篇文章主要介紹了詳解C++ 共享數(shù)據(jù)保護(hù)機(jī)制的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)使用c++,感興趣的朋友可以了解下2021-02-02在C語(yǔ)言里單引號(hào)和雙引號(hào)的區(qū)別
這篇文章主要介紹了在C語(yǔ)言里單引號(hào)和雙引號(hào)的區(qū)別,本文通過(guò)代碼的實(shí)例和注釋的詳細(xì)的說(shuō)明了單引號(hào)和雙引號(hào)的概念與區(qū)別,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07C++實(shí)現(xiàn)LeetCode(16.最近三數(shù)之和)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(16.最近三數(shù)之和),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07C語(yǔ)言嵌入informix基礎(chǔ)入門示例講解
這篇文章主要介紹了C語(yǔ)言嵌入informix基礎(chǔ)方法,大家參考使用2013-11-11C語(yǔ)言算法練習(xí)之?dāng)?shù)組元素排序
這篇文章主要為大家介紹了C語(yǔ)言算法練習(xí)中數(shù)組元素排序的實(shí)現(xiàn)方法,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)C語(yǔ)言有一定幫助,需要的可以參考一下2022-09-09C++ 重載與重寫的區(qū)別與實(shí)現(xiàn)
在面向?qū)ο笳Z(yǔ)言中,經(jīng)常提到重載與重寫,本文主要介紹了C++ 重載與重寫的區(qū)別與實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01