c++ primer 筆記第十三章拷貝控制
13章 拷貝控制
一個類定義五中特殊成員函式:拷貝建構函式、拷貝賦值運算子、移動建構函式、移動賦值運算子和解構函式。叫做拷貝控制操作。
13.1 拷貝、賦值與銷燬
13.1.1 拷貝建構函式
拷貝函式第一個引數是自身型別的引用,一般是常量引用,且額外引數都有預設值。
拷貝函式通常不是explicit的。
未定義拷貝建構函式時,編譯器自動合成一個合成拷貝建構函式。操作是直接拷貝每個成員或者呼叫成員的拷貝建構函式。
拷貝初始化與直接初始化的區別。拷貝初始化在使用=定義變數時候使用,以及一些其它情況。
函式呼叫非引用引數和函式返回非引用型別會呼叫拷貝初始化。解釋了拷貝函式引數是引用的原因。
不能隱式呼叫一個explicit形式的建構函式。
編譯器有時候可以跳過拷貝/移動建構函式,直接建立物件。但這時候拷貝/移動建構函式需要可訪問。
13.1.2 拷貝賦值運算子
如果類未定義拷貝賦值運算子,編譯器會為其合成。
某些運算子,包括賦值運算子,必須定義為成員函式。接受一個與其型別相同的引數。返回同一個指向左側物件的引用。
合成拷貝賦值運算子將右側運算物件每個非static成員賦予左側運算物件對應成員,或者禁止對該型別物件賦值。
13.1.3 解構函式
解構函式釋放物件使用的資源,並銷燬物件的非static資料成員。沒有返回值不接受引數的成員函式。不能被過載。
解構函式首先執行函式體,然後銷燬成員。成員按初始化順序逆序銷燬。通常釋放生存期分配的所有資源。
成員銷燬時發生什麼依賴於成員型別。類型別成員銷燬時執行解構函式,內建型別銷燬時什麼都不做。智慧指標是類型別。
呼叫解構函式:一、變數離開作用域;二、物件被銷燬時,它的成員呼叫;三、容器被銷燬時,元素也被銷燬;四、delete指向動態分配的物件的指標時;五、臨時物件在建立它的表示式結束時。
合成解構函式為阻止析構或者為空。解構函式本身不銷燬成員,而是在解構函式體之後隱含的析構階段進行銷燬。
13.1.4 三/五法則
如果一個類需要解構函式,那麼幾乎肯定需要拷貝建構函式和拷貝賦值運算子。
如果一個類需要拷貝建構函式,那麼幾乎肯定也需要拷貝賦值運算子。反之也是。
13.1.5 使用=default
使用=default修飾成員宣告,合成的函式將隱式宣告為內聯的。
13.1.6 阻止拷貝
某些情況需要類阻止拷貝或賦值。比如iostream類。
使用=delete宣告表示成員函式不能以任何方式使用。只能在第一次宣告的時候。可以對任意函式使用。
刪除解構函式的型別,編譯器不允許定義該型別變數或者建立該類臨時物件。可以動態分配但不能釋放。
合成的拷貝控制成員可能是刪除的。
新標準之前通過將拷貝建構函式和拷貝賦值運算子宣告為private來阻止拷貝。且可以宣告但不定義來防止友元拷貝。
13.2 拷貝控制和資源管理
一般管理類外資源的類必須定義拷貝控制函式。定義拷貝操作可以使類的行為類似於值或者指標。
13.2.1 行為像值的類
類似值行為的類需要對於類管理的資源,每個類的物件都有一份自己的拷貝。
賦值運算子考慮兩點:一、將一個物件賦予它本身,賦值運算子必須能正確工作。二、大多數賦值運算子組合了解構函式和拷貝建構函式的工作。
13.2.2 定義行為像指標的類
最好是使用shared_ptr共享指標來管理類中資源。
需要直接管理資源時使用引用計數。在共享記憶體中動態分配空間存放引用計數。
引用計數記錄的是與當前物件共享資源的物件的個數。
13.3 交換操作
除了定義拷貝控制成員,管理資源的類通常還定義一個名為swap的函式。
swap函式儘量交換指標而不是交換資源。一般定義為inline形式的友元函式。
std::swap函式應用於內建型別,對於類型別使用std::swap函式可能會增加拷貝。
在賦值運算子中使用swap,引數是非引用型別,直接與本物件進行swap。且自動處理自賦值和天然就是異常安全的。
13.6 物件移動
移動可以將一個類物件中的資源移動到一個新的物件中而不發生拷貝。新標準中容器可以儲存不可拷貝但能被移動的型別。
13.6.1 右值引用
右值引用是指必須繫結到右值的引用。&&來表示,且只能繫結到一個將要銷燬的物件。
常規引用(左值引用,&)不能繫結到要求轉換的表示式、字面常量或者返回右值的表示式。右值引用相反。
左值有持久的狀態,右值要麼是字面常量或者是表示式求值過程中建立的臨時物件。
變量表達式都是左值,因此不能將右值引用繫結到右值引用型別的變數上。
std::move函式將左值當做右值處理,除了對其賦值或者銷燬不再使用。
13.6.2 移動建構函式和移動賦值運算子
完成了資源移動之後,需要確保移後源物件進行銷燬是無害的。特別是源物件不再指向被移動的資源,這些資源已經歸屬於新建立的物件。
移動操作竊取資源而不分配任何資源,因此通常不丟擲異常。
在建構函式中引數列表和初始化列表新增noexcept: 表示該函式不會丟擲異常。不丟擲異常的移動函式必須標記noexcept。
一、移動操作通常不丟擲異常但是允許丟擲。二、標準庫容器需要對異常發生時的行為提供保障。需要noexcept告訴編譯器使用移動建構函式或者移動賦值運算子是可以安全使用的,否則可能不會被使用。
移動賦值運算子執行與解構函式和移動建構函式相同的操作。需要檢測自賦值之後釋放左側物件。
移後源物件需要可析構,一般使其指標為nullptr。但是移動操作必須保證物件依然是有效的。但不能假設移後源物件的值。
只有當一個類沒有定義任何自己版本的拷貝控制成員,且類的每個非static資料成員都可以移動時編譯器才會為其合成。
移動操作不會隱式定義為刪除的函式。現實要求編譯器=default移動操作,且編譯器不能移動所有成員則會將其定義為刪除的。
如果類定義了一個移動建構函式和/或一個移動賦值運算子,該類的合成拷貝兩函式定義為刪除的。
拷貝並賦值運算子可以同時得到拷貝賦值運算子和一個移動賦值運算子。
移動迭代器的解引用生成一個右引用。make_move_iterator函式將一個普通迭代器轉換為一個移動迭代器。
標準庫不保證哪些演算法適用移動迭代器。確信演算法在賦值之後不再訪問它,才能使用移動迭代器。
13.6.3 右值引用和成員函式
允許使用移動操作的成員函式一般有兩個版本,一個接受const 左值引用,另一個接受右值引用。
成員函式可以類似於const形式定義呼叫的物件必須為左值或者右值。但是必須在const之後。&代表左值,&&代表右值。
如果定義兩個或者兩個以上具有相同引數列表的成員函式,必須對所有函式加上引用限定符或者都不加。