C++: 拷貝管理

《C++ Primer》看到這一章我都驚呆了,C++竟然還能有這種功能:類可以控制該類型對象拷貝、賦值、移動和銷燬時做什麼。
以上操作通過:拷貝構造函數、移動構造函數、拷貝賦值運算符、移動賦值運算符、以及析構函數構成


拷貝構造函數:

定義: 如果一個構造函數的第一個參數是自身類類型的引用,且任何額外參數都有默認值,則此構造函數是拷貝構造函數。

本質上是個構造函數,所以它出現就會構造新對象,沒有新對象它就沒出現。

拷貝構造函數的第一個參數必須是一個引用類型,因爲如果是值傳遞方式穿參的話,會立即再次調用拷貝構造函數,這樣就形成無限遞歸了!危險!

如果我們沒有自定義一個拷貝構造函數,編譯器會自動幫我們合成一個。默認拷貝構造函數會將其參數成員逐個拷貝到正在創建的對象中。

以下情況會發生拷貝初始化:
1. 用=定義變量。
2. 將一個對象作爲實參傳遞給一個非引用類型的形參。
3. 從一個返回類型爲非引用類型的函數返回一個對象。
4. 列表初始化一個數組中的元素或一個聚合類中的成員。

有個小坑是,A aa = a;像這樣定義並初始化一個A類型的變量aa,只會調用拷貝構造函數,不會調用拷貝賦值運算符。

#include <iostream>
using namespace std;

class A{
public:
    A(int i=0):n(i){}

    A(A &a, int i=0):n(i){
        cout<< a.getI() <<endl;
        cout<< getI() <<endl;
        cout<<"hello"<<endl;
    }

    const int getI(){
        return n;
    }

private:
    int n = 0;
};

int main(){
    A a(998);
    A b = a;//調用拷貝構造函數
    //輸出結果爲:
    //998
    //0
    //hello
    //證明 a 被當成了拷貝構造函數的第一個參數
    return 0;
}

拷貝賦值運算符

這其實就是個操作符重載。本質上是個名爲operator=的函數,所有有形參有返回值,有函數體,都有。

當然要成爲一個偉大的賦值運算符,當然要付出一點代價:
1. 名字必須是operator=
2. 必須定義爲成員函數
3. 左側運算對象就綁定到隱式的this參數上
4. 接受一個與其所在類相同類型的參數
5. 爲了與內置類型的賦值保持一致,返回指向左側對象的引用。

如果類沒有定義自己的拷貝賦值操作符,編譯器會自動生成一個。它會將右側運算對象的每個非static成員賦予左側運算對象的對應成員。


#include <iostream>
using namespace std;

class A{
public:
    A(int i=0):n(i){}

    A(A &a, int i=0):n(i){
        cout<< a.getI() <<endl;
        cout<< getI() <<endl;
        cout<<"hello"<<endl;
    }

    A &operator=(const A &r){
        n = r.n;
        cout<<"copying..."<<endl;
        return *this;
    }

    const int getI(){
        return n;
    }

private:
    int n = 0;
};

int main(){
    A a(998);
    A b(333);
    b = a;
    cout<< b.getI()<<endl;
    //copying...
    //998
    return 0;
}

析構函數:

析構函數釋放對象使用的資源,並銷燬對象的非static數據成員。

跟構造函數一樣的是:沒有返回類型,如果沒自定義,編譯器就會生成一個默認的。
跟構造函數不一樣的是:沒有構造函數初始化列表,名字是類名前加一個波浪號~,析構函數不接受參數,所以不能被重載,不能定義爲delete。

note:析構函數先執行函數體,然後銷燬成員,順序按初始化的逆序。銷燬一個內置類型的指針不會delete它指向的對象。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章