C/C++基礎----拷貝控制
阿新 • • 發佈:2018-12-27
拷貝控制操作,有5個特殊成員函式copy ctor,copy =opt,move ctor,move =opt,dtor
有哪些地方會用到
拷貝初始化
除了=定義變數時
引數傳遞和函式返回時
花括號列表初始化一個數組中元素或一個聚合類中成員
某些類對所分配的物件使用拷貝初始化,如insert和push;相對地,emplace直接初始化
成員是在解構函式體之後隱含的析構階段中被銷燬的
1一個類需要自定義解構函式,幾乎可以肯定它也需要自定義拷貝構造和拷貝賦值。
2拷貝構造和拷貝賦值有一個,幾乎可以肯定另一個也需要,但是不一定需要解構函式。
- 某些類需要阻止拷貝
如iostream類,避免多個物件寫入或讀取相同的IO緩衝。 定義為刪除的函式來阻止拷貝。 struct NoCopy{ NoCopy() = default; //合成的預設建構函式 NoCopy(const NoCopy&) = delete; //阻止拷貝 NoCopy &operator=(const NoCopy&) = delete; //阻止賦值 ~NoCopy() = default; //合成版本 //其他 } =delete必須出現在第一次宣告的時候 =delete可以對任意函式指定,但不能刪除解構函式 如果一個類有資料成員不能預設構造、拷貝、賦值或銷燬,則對應的成員函式將被定義為刪除的。 希望阻止拷貝應該用=delete定義,而不應該宣告為private,成員函式和友元函式仍然可以進行拷貝。 管理類外資源的類,通常必須定義拷貝控制成員。首先需要確定此型別物件的拷貝語義,一般有兩種選擇,使類看起來像一個值或者一個指標。
- 兩種類
- 像值的類:需要深拷貝,定義copy ctor、copy =opt(自賦值)和析構
- 像指標類:使用shared_ptr來管理資源,使用引用計數,拷貝時修改計數,析構時判斷
- 定義類自己的swap
定義類自己的swap是一種優化手段,應該呼叫swap而不是std::swap。如果存在型別特定的swap版本,匹配程度會優於std版本。 定義了swap的類通常用swap來定義賦值運算子 HasPtr& HasPtr::operator=(HasPtr ths) { swap(*this, ths);//ths現在指向本物件曾經使用的記憶體,不能交換指標區域性變數會被銷燬 return *this;//rhs被銷燬 } 該方法還自動處理了自賦值情況且是天然異常安全的。 在改變左側運算物件之前拷貝右側運算物件 唯一可能丟擲異常的是拷貝建構函式中new表示式,真出現也會在改變左側之前就發生。
- 引用與move
左值引用,不能繫結到要求轉換的表示式、字面值常量、返回右值的表示式。 const左值引用或者右值引用可以繫結到右值上。 區別:左值有持久的狀態,右值要麼是字面常量,要麼是在表示式求值過程中建立的臨時物件。 所以右值引用的物件,將要被銷燬,且沒有其他使用者。這就意味著右值引用的程式碼可以自由地接管所引用物件的資源,“竊取”其狀態。 我們可以顯式地將一個左值轉換為對應的右值引用型別。還可以通過move來獲得繫結到左值的右值引用。<utility> int &&rr3=std::move(rr1); move告訴編譯器,我們有一個左值,但希望像一個右值一樣處理它。呼叫move意味著除了對rr1賦值和銷燬外,不能使用它的值。 移動一個物件資料並不會銷燬此物件,但有時移動完成後源物件會銷燬。所以編寫移動操作時,必須確保源物件進入可析構的安全狀態,還必須保證物件仍然是有效的(可以安全地賦予新值或者可以安全地使用而不依賴其當前值)。我們的程式不應該依賴於移後源物件的資料。 當編寫一個不丟擲異常的移動操作的時候,必須在宣告和定義中都指定noexcept。 1雖然移動操作通常不丟擲異常,但丟擲異常是允許的 2標準庫容器能對異常發生時自身的行為提供保證。 除非知道移動不會丟擲異常,否則必須用拷貝。同樣需要檢測自賦值,不能再使用右側資源前就釋放左側資源。 引數是左值,移動版本賦值不可行,不能隱式地將一個右值引用繫結到一個左值。 引數是右值,兩個版本都可以,拷貝=需要一次const轉換,移動=則是精確匹配。 所有5個拷貝控制成員應該看做一個整體。
- 移動迭代器
make_move_iterator,解引用後生成一個右值引用。
標準庫不保證哪些演算法適用於移動迭代器。只有在確信演算法在為一個元素賦值或者將其傳遞給一個使用者定義的函式後不再訪問它時,才能將移動迭代器傳遞給演算法。
引用限定符& &&,類似const限定符只能用於非static成員函式,且必須同時出現在宣告和定義中。const在前,&在後。
物件是一個右值,意味著沒有其他使用者,因此可以改變物件。