复制构造函数
复制构造函数是构造函数,它可以用相同类类型的参数调用,并在不改变参数内容的情况下复制参数的内容。
目录 |
[编辑] 语法
类名 ( 形参列表 ); |
(1) | ||||||||
类名 ( 形参列表 ) 函数体 |
(2) | ||||||||
类名 ( 单一形参列表 ) = default; |
(3) | (自 C++11 起) | |||||||
类名 ( 形参列表 ) = delete; |
(4) | (自 C++11 起) | |||||||
类名 :: 类名 ( 形参列表 ) 函数体 |
(5) | ||||||||
类名 :: 类名 ( 单一形参列表 ) = default; |
(6) | (自 C++11 起) | |||||||
类名 | - | 正在声明复制构造函数的类 |
形参列表 | - | 一个非空的形参列表,满足以下所有条件
|
单一形参列表 | - | 一个形参列表,仅包含一个参数,其类型为 T&、const T&、volatile T& 或 const volatile T&,并且没有默认实参 |
函数体 | - | 复制构造函数的函数体 |
[编辑] 解释
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); - 函数返回:return a; 在诸如 T f() 的函数内部,其中 a 的类型为
T
,并且没有移动构造函数。
[编辑] 隐式声明的复制构造函数
如果未为类类型提供用户定义的复制构造函数,则编译器将始终声明一个复制构造函数,作为其类的非显式 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 式使用或需要进行常量求值时(自 C++11 起),编译器会定义它(即,生成并编译函数体)。对于联合体类型,隐式定义的复制构造函数复制对象表示(如同使用 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
的复制构造函数的重载决议
- 不会产生可用的候选者,或者
- 在子对象为变体成员的情况下,选择了一个非平凡函数。
-
(自 C++11 起) |
[编辑] 平凡复制构造函数
如果以下所有条件均为真,则类 T
的复制构造函数是平凡的
- 它不是用户提供的(即,它是隐式定义的或预置的);
-
T
没有虚成员函数; -
T
没有虚基类; - 为
T
的每个直接基类选择的复制构造函数是平凡的; - 为
T
的每个非静态类类型(或类类型数组)成员选择的复制构造函数是平凡的;
非联合体类的平凡复制构造函数有效地复制实参的每个标量子对象(包括递归地,子对象的子对象等等),并且不执行其他操作。但是,填充字节不需要复制,甚至复制的子对象的对象表示形式也不需要相同,只要它们的值相同即可。
可平凡复制 (TriviallyCopyable) 对象可以通过手动复制其对象表示形式来复制,例如使用 std::memmove。所有与 C 语言兼容的数据类型(POD 类型)都是可平凡复制的。
[编辑] 合格复制构造函数
如果复制构造函数是用户声明的,或者既是隐式声明的又是可定义的,则它是合格的。 |
(C++11 前) |
如果复制构造函数未被删除,则它是合格的。 |
(自 C++11 起) (C++20 前) |
如果满足以下所有条件,则复制构造函数是合格的 |
(自 C++20 起) |
合格复制构造函数的平凡性决定了类是否为隐式生存期类型,以及类是否为可平凡复制类型。
[编辑] 注解
在许多情况下,即使复制构造函数会产生可观察的副作用,也会对其进行优化,请参见复制省略。
[编辑] 示例
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 issue 496) | 平凡性不受影响 |
CWG 2171 | C++11 | X(X&) = default 是非平凡的 | 变为平凡的 |
CWG 2595 | C++20 | 如果存在 另一个更受约束的复制构造函数,则复制构造函数不合格 但不满足其关联约束 |
在这种情况下,它可以是合格的 |