複製建構函式
複製建構函式(copy constructor)是一種建構函式,它可以用相同類別類型的參數來呼叫,並在不變動該參數的情況下,複製該參數的內容。
目錄 |
[編輯] 語法
class-name (parameter-list ); |
(1) | ||||||||
class-name (parameter-list ) function-body |
(2) | ||||||||
class-name (single-parameter-list ) = default; |
(3) | (C++11 起) | |||||||
class-name (parameter-list ) = delete; |
(4) | (C++11 起) | |||||||
class-name ::class-name (parameter-list ) function-body |
(5) | ||||||||
class-name ::class-name (single-parameter-list ) = default; |
(6) | (C++11 起) | |||||||
| class-name | - | 正在宣告其複製建構函式的類別 |
| 參數列表 | - | 一個非空的參數列表,滿足下列所有條件
|
| 單參數列表(single-parameter-list) | - | 一個僅包含一個參數的參數列表,該參數類型為 T&、const T&、volatile T& 或 const volatile T&,且沒有預設參數 |
| function-body | - | 複製建構函式的函式主體 |
[編輯] 說明
struct X { X(X& other); // copy constructor // X(X other); // Error: incorrect parameter type }; union Y { Y(Y& other, int num = 1); // copy constructor with multiple parameters // Y(Y& other, int num); // Error: `num` has no default argument };
每當從相同類型的另一個物件初始化(透過直接初始化或複製初始化)一個物件時,就會呼叫複製建構函式(除非多載解析選擇了更好的匹配,或者該呼叫被省略),這包括:
- 初始化:T a = b; 或 T a(b);,其中 b 的類型為
T; - 函式參數傳遞:f(a);,其中 a 的類型為
T,且 f 為 void f(T t); - 函式回傳:在諸如 T f() 的函式內執行 return a;,其中 a 的類型為
T,且該類型沒有移動建構函式。
[編輯] 隱式宣告的複製建構函式
若類別類型沒有提供使用者定義的複製建構函式,編譯器將始終宣告一個非顯式(explicit)的 inline public 成員作為該類別的複製建構函式。若滿足以下所有條件,此隱式宣告的複製建構函式形式為 T::T(const T&):
T的每個直接基底類別與虛擬基底類別B都有一個複製建構函式,其參數類型為 const B& 或 const volatile B&;T的每個類別類型(或類別類型陣列)的非靜態資料成員M都有一個複製建構函式,其參數類型為 const M& 或 const volatile M&。
否則,隱式宣告的複製建構函式為 T::T(T&)。
由於這些規則,隱式宣告的複製建構函式無法繫結至 volatile 左值參數。
一個類別可以擁有多個複製建構函式,例如同時擁有 T::T(const T&) 和 T::T(T&)。
|
即使存在某些使用者定義的複製建構函式,使用者仍可使用關鍵字 default 強制宣告隱式複製建構函式。 |
(C++11 起) |
隱式宣告(或在其首次宣告時預設)的複製建構函式具有異常規格,如 動態異常規格(C++17 前)noexcept 規格(C++17 起) 所述。
[編輯] 隱式定義的複製建構函式
若隱式宣告的複製建構函式未被刪除,則若它被 ODR 使用(odr-used)或為常數評估所需(C++11 起),編譯器會定義它(即產生並編譯函式主體)。對於聯集(union)類型,隱式定義的複製建構函式會複製物件表示(如 std::memmove)。對於非聯集的類別類型,建構函式會按照初始化順序,使用直接初始化對物件的直接基底子物件和成員子物件執行完整的成員逐一複製。對於每個參考類型的非靜態資料成員,複製建構函式會將該參考繫結至來源參考所繫結的相同物件或函式。
|
若此定義滿足 constexpr 建構函式(C++23 前)constexpr 函式(C++23 起) 的要求,則產生的複製建構函式為 constexpr。 若 |
(C++11 起) |
[編輯] 刪除的複製建構函式
若滿足下列任一條件,類別 T 的隱式宣告或顯式預設(C++11 起)複製建構函式將被未定義(C++11 前)定義為刪除的(C++11 起):
|
(C++11 起) |
-
T具有類別類型M(或其可能是多維陣列)的潛在構造子物件,使得
-
M的解構函式被刪除或(C++11 起)無法從複製建構函式存取,或 - 應用於尋找
M複製建構函式的多載解析結果
- 沒有產生可用的候選函式,或
- 在該子物件為變體成員 (variant member) 的情況下,選擇了非平凡函式。
-
| (C++11 起) |
[編輯] 平凡複製建構函式
若滿足以下所有條件,則類別 T 的複製建構函式是平凡的:
- 它不是使用者提供的(即它是隱式定義的或預設的);
-
T沒有虛擬成員函式; -
T沒有虛擬基類; - 為
T的每個直接基底類別選擇的複製建構函式是平凡的; - 為
T的每個非靜態類別類型(或類別類型陣列)成員選擇的複製建構函式是平凡的;
對於非聯集類別,平凡複製建構函式會有效地複製參數的每個純量子物件(包括遞迴地複製子物件的子物件等),且不執行其他任何操作。然而,填充位元組(padding bytes)不需要被複製,甚至只要複製的子物件的值相同,它們的物件表示也不必相同。
TriviallyCopyable(平凡可複製)物件可以透過手動複製其物件表示來複製,例如使用 std::memmove。所有與 C 語言相容的資料類型(POD 類型)都是平凡可複製的。
[編輯] 合格的複製建構函式
|
若複製建構函式是使用者宣告的,或是隱式宣告且可定義的,則它就是合格的。 |
(直到 C++11) |
|
若複製建構函式未被刪除,則它是合格的。 |
(C++11 起) (直到 C++20) |
|
若滿足以下所有條件,則複製建構函式是合格的:
|
(自 C++20 起) |
合格複製建構函式的平凡性決定了該類別是否為隱式生命週期類型,以及該類別是否為平凡可複製類型。
[編輯] 註解
在許多情況下,即使複製建構函式會產生可觀察的副作用,它們也會被最佳化移除,參見複製省略(copy elision)。
[編輯] 範例
struct A { int n; A(int n = 1) : n(n) {} A(const A& a) : n(a.n) {} // user-defined copy constructor }; struct B : A { // implicit default constructor B::B() // implicit copy constructor B::B(const B&) }; struct C : B { C() : B() {} private: C(const C&); // non-copyable, C++98 style }; int main() { A a1(7); A a2(a1); // calls the copy constructor B b; B b2 = b; A a3 = b; // conversion to A& and copy constructor volatile A va(10); // A a4 = va; // compile error C c; // C c2 = c; // compile error }
[編輯] 缺陷報告
下列更改行為的缺陷報告追溯應用於之前的 C++ 標準。
| DR | 應用於 | 出版時的行為 | 正確的行為 |
|---|---|---|---|
| CWG 1353 | C++98 | 隱式宣告的複製建構函式為未定義的條件 未考慮多維陣列類型 |
考量這些型別 |
| CWG 2094 | C++11 | volatile 成員使複製變得不平凡(CWG 問題 496) | 平凡性不受影響 |
| CWG 2171 | C++11 | X(X&) = default 是不平凡的 | 改為平凡的 |
| CWG 2595 | C++20 | 若存在另一個更受約束的複製建構函式, 則複製建構函式不合格 但如果該移動建構函式未滿足其相關限制 |
在此情況下它可以是合格的 |