複製賦值運算子
複製賦值運算子是一個非模板非靜態成員函式,其名稱為 operator=,可以接受相同類型別的引數,並複製引數的內容而不改變引數。
目錄 |
[編輯] 語法
有關正式的複製賦值運算子語法,請參閱函式宣告。以下語法列表僅演示了所有有效複製賦值運算子語法的一個子集。
返回型別 operator=( 引數列表 ); |
(1) | ||||||||
返回型別 operator=( 引數列表 ) 函式體 |
(2) | ||||||||
返回型別 operator=( 無預設引數的引數列表 ) = default; |
(3) | (C++11 起) | |||||||
返回型別 operator=( 引數列表 ) = delete; |
(4) | (C++11 起) | |||||||
返回型別 類名 :: operator=( 引數列表 ) 函式體 |
(5) | ||||||||
返回型別 類名 :: operator=( 無預設引數的引數列表 ) = default; |
(6) | (C++11 起) | |||||||
類名 | - | 宣告其複製賦值運算子的類,在下面的描述中類型別表示為 T |
引數列表 | - | 僅包含一個引數的引數列表,該引數型別為 T , T& , const T&, volatile T& 或 const volatile T& |
無預設引數的引數列表 | - | 僅包含一個引數的引數列表,該引數型別為 T , T& , const T&, volatile T& 或 const volatile T& 且不具有預設引數 |
函式體 | - | 複製賦值運算子的函式體 |
返回型別 | - | 任何型別,但為了允許鏈式賦值,更推薦使用 T& |
[編輯] 解釋
struct X { X& operator=(X& other); // copy assignment operator X operator=(X other); // pass-by-value is allowed // X operator=(const X other); // Error: incorrect parameter type }; union Y { // copy assignment operators can have syntaxes not listed above, // as long as they follow the general function declaration syntax // and do not viloate the restrictions listed above auto operator=(Y& other) -> Y&; // OK: trailing return type Y& operator=(this Y& self, Y& other); // OK: explicit object parameter // Y& operator=(Y&, int num = 1); // Error: has other non-object parameters };
複製賦值運算子在過載決議選中時被呼叫,例如當一個物件出現在賦值表示式的左側時。
[編輯] 隱式宣告的複製賦值運算子
如果一個類型別沒有提供使用者定義的複製賦值運算子,編譯器將始終宣告一個作為類的inline public成員。如果滿足以下所有條件,則此隱式宣告的複製賦值運算子具有 T& T::operator=(const T&) 的形式:
T
的每個直接基類B
都有一個複製賦值運算子,其引數是B
或 const B& 或 const volatile B&;T
的每個類型別或類型別陣列的非靜態資料成員M
都有一個複製賦值運算子,其引數是M
或 const M& 或 const volatile M&。
否則,隱式宣告的複製賦值運算子被宣告為 T& T::operator=(T&)。
由於這些規則,隱式宣告的複製賦值運算子無法繫結到 volatile 左值引數。
一個類可以有多個複製賦值運算子,例如 T& T::operator=(T&) 和 T& T::operator=(T)。 如果存在某些使用者定義的複製賦值運算子,使用者仍然可以使用關鍵字 default 強制生成隱式宣告的複製賦值運算子。(C++11 起)
隱式宣告的(或在其第一次宣告時預設的)複製賦值運算子具有一個異常規範,如動態異常規範(C++17 前)noexcept 規範(C++17 起)中所述。
因為複製賦值運算子總是為任何類宣告,所以基類賦值運算子總是被隱藏。如果使用using-宣告來引入基類的賦值運算子,並且其引數型別可能與派生類的隱式賦值運算子的引數型別相同,則 using-宣告也被隱式宣告隱藏。
[編輯] 隱式定義的複製賦值運算子
如果隱式宣告的複製賦值運算子既未被刪除也非平凡,則當它被odr-使用或需要用於常量求值(C++14 起)時,編譯器會定義它(即生成並編譯函式體)。對於聯合型別,隱式定義的複製賦值運算子會複製物件表示(如同透過std::memmove)。對於非聯合類型別,該運算子按照物件的直接基類和非靜態資料成員的初始化順序執行成員複製賦值,對標量使用內建賦值,對陣列使用成員複製賦值,對類型別使用複製賦值運算子(非虛呼叫)。
類
|
(C++14 起) (直至 C++23) |
類 |
(C++23 起) |
如果 |
(C++11 起) |
[編輯] 已刪除的複製賦值運算子
如果滿足以下任何條件,則類 T
的隱式宣告的或顯式預設的(C++11 起)複製賦值運算子是未定義的(C++11 前)定義為已刪除的(C++11 起):
-
T
具有 const 限定的非類型別(或其可能的二維陣列)的非靜態資料成員。 -
T
具有引用型別的非靜態資料成員。 -
T
具有類型別M
(或其可能的二維陣列)的潛在構造子物件,以至於應用於查詢M
的複製賦值運算子的過載決議
- 未產生可用的候選,或者
- 在子物件是變體成員的情況下,選擇了一個非平凡函式。
(C++11 起) |
[編輯] 平凡複製賦值運算子
如果滿足以下所有條件,則類 T
的複製賦值運算子是平凡的:
- 它不是使用者提供的(意味著它是隱式定義或預設的);
-
T
沒有虛成員函式; -
T
沒有虛基類; - 為
T
的每個直接基類選擇的複製賦值運算子是平凡的; - 為
T
的每個非靜態類型別(或類型別陣列)成員選擇的複製賦值運算子是平凡的。
平凡的複製賦值運算子會複製物件表示,如同透過std::memmove。所有與 C 語言相容的資料型別(POD 型別)都是可平凡複製賦值的。
[編輯] 符合條件的複製賦值運算子
如果複製賦值運算子是使用者宣告的,或者既是隱式宣告的又是可定義的,則它是符合條件的。 |
(C++11 前) |
如果複製賦值運算子未被刪除,則它是符合條件的。 |
(C++11 起) (C++20 前) |
如果滿足以下所有條件,則複製賦值運算子是符合條件的: |
(C++20 起) |
符合條件的複製賦值運算子的平凡性決定了類是否為可平凡複製型別。
[編輯] 注意
如果同時提供了複製和移動賦值運算子,當引數是右值(無論是無名臨時物件之類的純右值,還是std::move結果之類的將亡值),過載決議會選擇移動賦值;當引數是左值(具名物件或返回左值引用的函式/運算子)時,過載決議會選擇複製賦值。如果只提供了複製賦值運算子,所有引數類別都會選擇它(只要它透過值或 const 引用接受引數,因為右值可以繫結到 const 引用),這使得複製賦值在移動賦值不可用時充當備用方案。
透過繼承層次結構中多於一條路徑可訪問的虛基類子物件,是否會被隱式定義的複製賦值運算子(同樣適用於移動賦值)多次賦值,這是未指定的。
有關使用者定義的複製賦值運算子的預期行為的更多詳細資訊,請參閱賦值運算子過載。
[編輯] 示例
#include <algorithm> #include <iostream> #include <memory> #include <string> struct A { int n; std::string s1; A() = default; A(A const&) = default; // user-defined copy assignment (copy-and-swap idiom) A& operator=(A other) { std::cout << "copy assignment of A\n"; std::swap(n, other.n); std::swap(s1, other.s1); return *this; } }; struct B : A { std::string s2; // implicitly-defined copy assignment }; struct C { std::unique_ptr<int[]> data; std::size_t size; // user-defined copy assignment (non copy-and-swap idiom) // note: copy-and-swap would always reallocate resources C& operator=(const C& other) { if (this != &other) // not a self-assignment { if (size != other.size) // resource cannot be reused { data.reset(new int[other.size]); size = other.size; } std::copy(&other.data[0], &other.data[0] + size, &data[0]); } return *this; } }; int main() { A a1, a2; std::cout << "a1 = a2 calls "; a1 = a2; // user-defined copy assignment B b1, b2; b2.s1 = "foo"; b2.s2 = "bar"; std::cout << "b1 = b2 calls "; b1 = b2; // implicitly-defined copy assignment std::cout << "b1.s1 = " << b1.s1 << "; b1.s2 = " << b1.s2 << '\n'; }
輸出
a1 = a2 calls copy assignment of A b1 = b2 calls copy assignment of A b1.s1 = foo; b1.s2 = bar
[編輯] 缺陷報告
下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。
缺陷報告 | 應用於 | 釋出時的行為 | 正確的行為 |
---|---|---|---|
CWG 1353 | C++98 | 隱式宣告的複製賦值運算子的條件 未考慮多維陣列型別時是未定義的 |
考慮這些型別 |
CWG 2094 | C++11 | volatile 子物件使預設的複製 賦值運算子變為非平凡(CWG issue 496) |
平凡性不受影響 |
CWG 2171 | C++11 | operator=(X&) = default 是非平凡的 | 使其變為平凡 |
CWG 2180 | C++11 | 類 T 的預設複製賦值運算子未定義為已刪除如果 T 是抽象的並且具有不可複製賦值的直接虛基類 |
該運算子在這種情況下被定義 為已刪除 |
CWG 2595 | C++20 | 如果存在另一個約束更強但其關聯約束不滿足的複製賦值運算子,則複製賦值運算子不符合條件 它也可以符合條件 它也可以符合條件 |
它可以是合格的 在這種情況下 |