拷贝构造函数
拷贝构造函数是一种构造函数,它可以使用相同类类型的参数调用,并复制参数的内容而不改变参数。
目录 |
[编辑] 语法
类名 ( 参数列表 ); |
(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); - 函数返回:函数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-use时 或 常量求值需要(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
的每个非静态类类型(或类类型数组)成员选择的拷贝构造函数是平凡的;
非联合类的平凡拷贝构造函数实际上会复制参数的每个标量子对象(包括递归地,子对象的子对象等),并且不执行其他操作。然而,填充字节不必复制,即使复制的子对象的对象表示不必相同,只要它们的值相同即可。
平凡可复制对象可以通过手动复制其对象表示来复制,例如使用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++ 标准。
缺陷报告 | 应用于 | 发布时的行为 | 正确的行为 |
---|---|---|---|
CWG 1353 | C++98 | 隐式声明的拷贝构造函数的条件 未定义时未考虑多维数组类型 |
考虑这些类型 |
CWG 2094 | C++11 | volatile 成员使拷贝非平凡(CWG 问题 496) | 平凡性不受影响 |
CWG 2171 | C++11 | X(X&) = default 曾是非平凡的 | 现在是平凡的 |
CWG 2595 | C++20 | 如果存在另一个拷贝构造函数,它更受约束 但没有满足其关联约束 则拷贝构造函数不合格 |
它在这种情况下可以合格 |