一般,咱們會按以下方式書寫拷貝構造函數:函數
class LiF { public: LiF(int _lif = 0) : lif(_lif) {} // 默認構造函數 LiF(const LiF& l) : lif(l.lif) {} // 拷貝構造函數 private: int lif; };
這是正確的。可是,若是數據成員包含指針類型的話,這種寫法就很危險了。this
class LiF { public: LiF() { lif = new int(0); } // 爲lif動態分配內存 LiF(const LiF& l) : lif(l.lif) {} // 拷貝構造函數 ~LiF() { // 析構函數 delete lif; // 釋放分配給lif的資源 lif = nullptr; // 置空 } private: int* lif; }; LiF l1; LiF l2(l1); // 程序結束析構l2時,程序將崩潰
在拷貝l1
生成l2
的時候,咱們的構造函數只是簡單的把l1
的lif
成員的值賦予了l2
的lif
,也就是說,它們保存的都是l1
構造時分配的地址,當二者之中的某個對象被銷燬時,構造函數正常執行,資源被釋放,但以後若是另外一個對象也被析構,lif
的資源就會被重複釋放,lif
也就變成野指針。這種拷貝方式也稱爲淺拷貝,即只拷貝空間,不拷貝資源。指針
爲了防止指針類型的數據成員出現野指針錯誤,對應地便有了深拷貝操做,即在拷貝對象內容的同時爲拷貝的內容分配新的資源。code
class LiF { public: LiF() { lif = new int(0); } // 爲lif動態分配內存 LiF(const LiF& l) : lif(new int(*l.lif)) {} // 深拷貝構造函數 ~LiF() { // 析構函數 delete lif; // 釋放分配給lif的資源 lif = nullptr; // 置空 } private: int* lif; }; LiF l1; LiF l2(l1);
注意到,在上面的拷貝構造函數中,咱們爲新對象的lif
成員分配了一塊新的內存,即完成了深拷貝。對象
從上面的例子能夠獲得兩種抽象的類行爲:行爲像值、行爲像指針。內存
即類提供的構造函數是深拷貝,類的每一個對象都有本身的一份拷貝。對於這樣的類,它顯然須要:一個深拷貝構造函數、一個深拷貝賦值運算符重載、一個能夠釋放成員佔用的資源的析構函數。資源
class LiF { public: LiF(const int& _lif = 0): lif(new int(_lif)) {} LiF(const LiF& l) : lif(new int(*l.lif)) {} LiF& operator= (const LiF& l) { lif = new int(*l.lif); return *this; } ~LiF() { delete lif; lif = nullptr; } private: int* lif; };
即類提供的是淺拷貝,但因爲可能有多個對象成員值相同一段內存,因此咱們不能在析構時簡單地釋放資源。爲了解決淺拷貝帶來的野指針問題,須要引入一種技術——引用計數(reference count)。這也是C++11的智能指針shared_ptr的實現。class
引用計數:構造函數
class LiF { public: LiF(const int& _lif = 0): lif(new int(_lif)), referenceCount(new unsigned(1)) {} LiF(const LiF& l) : lif(l.lif), referenceCount(l.referenceCount) { ++ *referenceCount; } LiF& operator= (const LiF& l) { ++ *l.referenceCount; if (-- *referenceCount == 0) { delete lif; delete referenceCount; } lif = l.lif; referenceCount = l.referenceCount; return *this; } ~LiF() { if (-- *referenceCount == 0) { delete lif; delete referenceCount; } } private: int *lif; unsigned *referenceCount; };