C++:Copy & Reference Count

淺拷貝、深拷貝

一般,咱們會按以下方式書寫拷貝構造函數:函數

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的時候,咱們的構造函數只是簡單的把l1lif成員的值賦予了l2lif,也就是說,它們保存的都是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

引用計數:構造函數

  • 在每一個構造函數初始化對象時,額外建立一個引用計數並置爲1,用以記錄有多少對象正在共享資源。
  • 在執行拷貝操做時進行淺拷貝,同時拷貝計數器,並遞增計數器,指出共享的對象增長了一個。
  • 在進行拷貝賦值時比較特殊但也很容易理解:須要遞增右側對象的計數器並遞減左側對象的計數器。若左側對象引用計數歸零,則釋放資源。
  • 在析構對象時,並不直接釋放共享的資源,而是遞減計數器,直至計數器歸零才釋放資源。
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;
};
相關文章
相關標籤/搜索