C++面試題之淺拷貝和深拷貝的區(qū)別
? ? ? ?先考慮一種情況,對(duì)一個(gè)已知對(duì)象進(jìn)行拷貝,編譯系統(tǒng)會(huì)自動(dòng)調(diào)用一種構(gòu)造函數(shù)——拷貝構(gòu)造函數(shù),如果用戶(hù)未定義拷貝構(gòu)造函數(shù),則會(huì)調(diào)用默認(rèn)拷貝構(gòu)造函數(shù)。
? ? ? ?先看一個(gè)例子,有一個(gè)學(xué)生類(lèi),數(shù)據(jù)成員時(shí)學(xué)生的人數(shù)和名字:
#includeusing?namespace?std; class?Student { private: int?num; char?*name; public: Student(); ~Student(); }; Student::Student() { name?=?new?char(20); cout?<<?"Student"?<<?endl; } Student::~Student() { cout?<<?"~Student?"?<<?(int)name?<<?endl; delete?name; name?=?NULL; } int?main() { {//?花括號(hào)讓s1和s2變成局部對(duì)象,方便測(cè)試 Student?s1; Student?s2(s1);//?復(fù)制對(duì)象 } system("pause"); return?0; }
? ? ? ?執(zhí)行結(jié)果:調(diào)用一次構(gòu)造函數(shù),調(diào)用兩次析構(gòu)函數(shù),兩個(gè)對(duì)象的指針成員所指內(nèi)存相同,這會(huì)導(dǎo)致什么問(wèn)題呢?name指針被分配一次內(nèi)存,但是程序結(jié)束時(shí)該內(nèi)存卻被釋放了兩次,會(huì)導(dǎo)致崩潰!
? ? ? ?這是由于編譯系統(tǒng)在我們沒(méi)有自己定義拷貝構(gòu)造函數(shù)時(shí),會(huì)在拷貝對(duì)象時(shí)調(diào)用默認(rèn)拷貝構(gòu)造函數(shù),進(jìn)行的是淺拷貝!即對(duì)指針name拷貝后會(huì)出現(xiàn)兩個(gè)指針指向同一個(gè)內(nèi)存空間。
? ? ? ?所以,在對(duì)含有指針成員的對(duì)象進(jìn)行拷貝時(shí),必須要自己定義拷貝構(gòu)造函數(shù),使拷貝后的對(duì)象指針成員有自己的內(nèi)存空間,即進(jìn)行深拷貝,這樣就避免了內(nèi)存泄漏發(fā)生。
? ? ? ? 添加了自己定義拷貝構(gòu)造函數(shù)的例子:
#includeusing?namespace?std; class?Student { private: int?num; char?*name; public: Student(); ~Student(); Student(const?Student?&s);//拷貝構(gòu)造函數(shù),const防止對(duì)象被改變 }; Student::Student() { name?=?new?char(20); cout?<<?"Student"?<<?endl; } Student::~Student() { cout?<<?"~Student?"?<<?(int)name?<<?endl; delete?name; name?=?NULL; } Student::Student(const?Student?&s) { name?=?new?char(20); memcpy(name,?s.name,?strlen(s.name)); cout?<<?"copy?Student"?<<?endl; } int?main() { {//?花括號(hào)讓s1和s2變成局部對(duì)象,方便測(cè)試 Student?s1; Student?s2(s1);//?復(fù)制對(duì)象 } system("pause"); return?0; }
? ? ? ? 執(zhí)行結(jié)果:調(diào)用一次構(gòu)造函數(shù),一次自定義拷貝構(gòu)造函數(shù),兩次析構(gòu)函數(shù)。兩個(gè)對(duì)象的指針成員所指內(nèi)存不同。
?總結(jié):淺拷貝只是對(duì)指針的拷貝,拷貝后兩個(gè)指針指向同一個(gè)內(nèi)存空間,深拷貝不但對(duì)指針進(jìn)行拷貝,而且對(duì)指針指向的內(nèi)容進(jìn)行拷貝,經(jīng)深拷貝后的指針是指向兩個(gè)不同地址的指針。
再說(shuō)幾句:
當(dāng)對(duì)象中存在指針成員時(shí),除了在復(fù)制對(duì)象時(shí)需要考慮自定義拷貝構(gòu)造函數(shù),還應(yīng)該考慮以下兩種情形:
1.當(dāng)函數(shù)的參數(shù)為對(duì)象時(shí),實(shí)參傳遞給形參的實(shí)際上是實(shí)參的一個(gè)拷貝對(duì)象,系統(tǒng)自動(dòng)通過(guò)拷貝構(gòu)造函數(shù)實(shí)現(xiàn);
2.當(dāng)函數(shù)的返回值為一個(gè)對(duì)象時(shí),該對(duì)象實(shí)際上是函數(shù)內(nèi)對(duì)象的一個(gè)拷貝,用于返回函數(shù)調(diào)用處。
3.淺拷貝帶來(lái)問(wèn)題的本質(zhì)在于析構(gòu)函數(shù)釋放多次堆內(nèi)存,使用std::shared_ptr,可以完美解決這個(gè)問(wèn)題。
關(guān)于std::shared_ptr的原理和實(shí)現(xiàn)可參考:C++筆試題之smart pointer的實(shí)現(xiàn)
一個(gè)完整的自定義類(lèi)實(shí)現(xiàn)可參考:C++筆試題之String類(lèi)的實(shí)現(xiàn)