本文主要介紹了拷貝建構函式和賦值運算子的區別,以及在什麼時候調用拷貝建構函式、什麼情況下調用賦值運算子。最後,簡單的分析了下深拷貝和淺拷貝的問題。
拷貝建構函式和賦值運算子
在預設情況下(使用者沒有定義,但是也沒有顯式的刪除),編譯器會自動的隱式產生一個拷貝建構函式和賦值運算子。但使用者可以使用delete來指定不產生拷貝建構函式和賦值運算子,這樣的對象就不能通過值傳遞,也不能進行賦值運算。
class Person{public: Person(const Person& p) = delete; Person& operator=(const Person& p) = delete;private: int age; string name;};
上面的定義的類Person顯式的刪除了拷貝建構函式和賦值運算子,在需要調用拷貝建構函式或者賦值運算子的地方,會提示_無法調用該函數,它是已刪除的函數_。
還有一點需要注意的是,拷貝建構函式必須以引用的方式傳遞參數。這是因為,在值傳遞的方式傳遞給一個函數的時候,會調用拷貝建構函式產生函數的實參。如果拷貝建構函式的參數仍然是以值的方式,就會無限迴圈的調用下去,直到函數的棧溢出。
何時調用
拷貝建構函式和賦值運算子的行為比較相似,都是將一個對象的值複製給另一個對象;但是其結果卻有些不同,拷貝建構函式使用傳入對象的值產生一個新的對象的執行個體,而賦值運算子是將對象的值複製給一個已經存在的執行個體。這種區別從兩者的名字也可以很輕易的分辨出來,拷貝建構函式也是一種建構函式,那麼它的功能就是建立一個新的對象執行個體;賦值運算子是執行某種運算,將一個對象的值複製給另一個對象(已經存在的)。調用的是拷貝建構函式還是賦值運算子,主要是看是否有新的對象執行個體產生。如果產生了新的對象執行個體,那調用的就是拷貝建構函式;如果沒有,那就是對已有的對象賦值,調用的是賦值運算子。
調用拷貝建構函式主要有以下情境:
- 對象作為函數的參數,以值傳遞的方式傳給函數。
- 對象作為函數的傳回值,以值的方式從函數返回
- 使用一個對象給另一個對象初始化
代碼如下:
class Person{public: Person(){} Person(const Person& p) { cout << "Copy Constructor" << endl; } Person& operator=(const Person& p) { cout << "Assign" << endl; return *this; }private: int age; string name;};void f(Person p){ return;}Person f1(){ Person p; return p;}int main(){ Person p; Person p1 = p; // 1 Person p2; p2 = p; // 2 f(p2); // 3 p2 = f1(); // 4 Person p3 = f1(); // 5 getchar(); return 0;}
上面代碼中定義了一個類Person,顯式的定義了拷貝建構函式和賦值運算子。然後定義了兩個函數:f,以值的方式參傳入Person對象;f1,以值的方式返回Person對象。在main中類比了5中情境,測試調用的是拷貝建構函式還是賦值運算子。執行結果如下:
分析如下:
- 這是雖然使用了"=",但是實際上使用對象p來建立一個新的對象p1。也就是產生了新的對象,所以調用的是拷貝建構函式。
- 首先聲明一個對象p2,然後使用賦值運算子"=",將p的值複製給p2,顯然是調用賦值運算子,為一個已經存在的對象賦值 。
- 以值傳遞的方式將對象p2傳入函數f內,調用拷貝建構函式構建一個函數f可用的實參。
- 這條語句拷貝建構函式和賦值運算子都調用了。函數f1以值的方式返回一個Person對象,在返回時會調用拷貝建構函式建立一個臨時對象tmp作為傳回值;返回後調用賦值運算子將臨時對象tmp賦值給p2.
- 按照4的解釋,應該是首先調用拷貝建構函式建立臨時對象;然後再調用拷貝建構函式使用剛才建立的臨時對象建立新的對象p3,也就是會調用兩次拷貝建構函式。不過,編譯器也沒有那麼傻,應該是直接調用拷貝建構函式使用傳回值建立了對象p3。
深拷貝、淺拷貝
說到拷貝建構函式,就不得不提深拷貝和淺拷貝。通常,預設產生的拷貝建構函式和賦值運算子,只是簡單的進行值的複製。例如:上面的Person類,欄位只有int和string兩種類型,這在拷貝或者賦值時進行值複製建立的出來的對象和來源物件也是沒有任何關聯,對來源物件的任何操作都不會影響到拷貝出來的對象。反之,假如Person有一個對象為int *,這時在拷貝時還只是進行值複製,那麼建立出來的Person對象的int *的值就和來源物件的int *指向的是同一個位置。任何一個對象對該值的修改都會影響到另一個對象,這種情況就是淺拷貝。
深拷貝和淺拷貝主要是針對類中的指標和動態分配的空間來說的,因為對於指標只是簡單的值複製並不能分割開兩個對象的關聯,任何一個對象對該指標的操作都會影響到另一個對象。這時候就需要提供自訂的深拷貝的拷貝建構函式,消除這種影響。通常的原則是:
- 含有指標類型的成員或者有動態分配記憶體的成員都應該提供自訂的拷貝建構函式
- 在提供拷貝建構函式的同時,還應該考慮實現自訂的賦值運算子
對於拷貝建構函式的實現要確保以下幾點:
- 對於實值型別的成員進行值複製
- 對於指標和動態分配的空間,在拷貝中應重新分配分配空間
- 對於基類,要調用基類合適的拷貝方法,完成基類的拷貝
總結
- 拷貝建構函式和賦值運算子的行為比較相似,卻產生不同的結果;拷貝建構函式使用已有的對象建立一個新的對象,賦值運算子是將一個對象的值複製給另一個已存在的對象。區分是調用拷貝建構函式還是賦值運算子,主要是否有新的對象產生。
- 關於深拷貝和淺拷貝。當類有指標成員或有動態分配空間,都應實現自訂的拷貝建構函式。提供了拷貝建構函式,最後也實現賦值運算子。
以上就是本文的全部內容,希望本文的內容對大家的學習或者工作能帶來一定的協助,如果有疑問大家可以留言交流,同時也希望多多支援雲棲社區!