命名空间
变体
操作

复制构造函数

来自 cppreference.com
< cpp‎ | language
 
 
C++ 语言
 
 

复制构造函数是一个 构造函数,它可以使用相同类类型的参数调用,并复制参数的内容,而不会修改参数。

内容

[编辑] 语法

类名 (参数列表 ); (1)
类名 (参数列表 ) 函数体 (2)
类名 (单参数列表 ) = default; (3) (自 C++11 起)
类名 (参数列表 ) = delete; (4) (自 C++11 起)
类名 ::类名 (参数列表 ) 函数体 (5)
类名 ::类名 (单参数列表 ) = default; (6) (自 C++11 起)
类名 - 要声明复制构造函数的类
参数列表 - 满足以下所有条件的非空 参数列表
  • 假设类类型为 T,则第一个参数的类型为 T&const T&volatile T&const volatile T&,并且
  • 要么没有其他参数,要么所有其他参数都具有 默认参数
单参数列表 - 仅包含一个参数的 参数列表,该参数的类型为 T&const T&volatile T&const volatile T&,并且没有默认参数
函数体 - 复制构造函数的 函数体

[编辑] 解释

1) 在类定义内部声明复制构造函数。
2-4) 在类定义内部定义复制构造函数。
3) 复制构造函数被显式默认。
4) 复制构造函数被删除。
5,6) 在类定义外部定义复制构造函数(类必须包含声明 (1))。
6) 复制构造函数被显式默认。
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,而fvoid f(T t)
  • 函数返回:return a;在诸如T f()之类的函数内部,其中a是类型T,并且没有移动构造函数

[edit] 隐式声明的拷贝构造函数

如果未为类类型提供任何用户定义的拷贝构造函数,编译器将始终将拷贝构造函数声明为其类的非显式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 起)中所述。

[edit] 隐式定义的拷贝构造函数

如果隐式声明的拷贝构造函数未被删除,则如果odr-使用需要进行常量求值(自 C++11 起),编译器将对其进行定义(即生成函数主体并编译)。对于联合类型,隐式定义的拷贝构造函数将复制对象表示(如std::memmove)。对于非联合类类型,构造函数使用直接初始化执行对象的直接基子对象和成员子对象的按其初始化顺序的全成员复制。对于引用类型的每个非静态数据成员,拷贝构造函数将引用绑定到源引用绑定的同一个对象或函数。

如果这满足constexpr 构造函数(直到 C++23)constexpr 函数(自 C++23 起)的要求,则生成的拷贝构造函数为constexpr

如果T具有用户定义的析构函数或用户定义的拷贝赋值运算符,则隐式定义的拷贝构造函数的生成已弃用。

(自 C++11 起)

[edit] 删除的拷贝构造函数

如果满足以下任何条件,则类T的隐式声明的或显式默认为(自 C++11 起)拷贝构造函数未定义(直到 C++11)定义为删除(自 C++11 起)

  • T具有右值引用类型的非静态数据成员。
(自 C++11 起)
  • M具有删除或(自 C++11 起)从拷贝构造函数中无法访问的析构函数,或者
  • 应用于查找M的拷贝构造函数的重载解析
  • 不会导致可用的候选者,或者
  • 在子对象是变体成员的情况下,选择非平凡函数。

如果T声明了移动构造函数移动赋值运算符,则类T的隐式声明的拷贝构造函数定义为删除。

(自 C++11 起)

[edit] 平凡的拷贝构造函数

如果以下所有条件都为真,则类T的拷贝构造函数为平凡的

  • 它不是用户提供的(即它为隐式定义或默认为);
  • T没有虚成员函数;
  • T没有虚基类;
  • T的每个直接基类选择的拷贝构造函数为平凡的;
  • T的每个非静态类类型(或类类型数组)成员选择的拷贝构造函数为平凡的;

非联合类的平凡拷贝构造函数实际上复制了参数的每个标量子对象(包括递归地复制子对象等的子对象),并且不执行其他操作。但是,填充字节不必复制,即使复制的子对象的表示形式不必相同,只要它们的的值相同即可。

TriviallyCopyable对象可以通过手动复制其对象表示形式来复制,例如使用std::memmove。与 C 语言兼容的所有数据类型(POD 类型)都是平凡可复制的。

[edit] 合格的拷贝构造函数

如果拷贝构造函数是用户声明的,或者既是隐式声明的又是可定义的,则该拷贝构造函数是合格的。

(直到 C++11)

如果拷贝构造函数未被删除,则该拷贝构造函数是合格的。

(自 C++11 起)
(直到 C++20)

如果满足以下所有条件,则拷贝构造函数是合格的

  • 它未被删除。
  • 关联约束(如果有)得到满足。
  • 在所有满足关联约束的拷贝构造函数中,它比任何其他拷贝构造函数约束更多
(自 C++20 起)

合格拷贝构造函数的平凡性决定了该类是否为隐式生存期类型,以及该类是否为平凡可复制类型

[edit] 备注

在许多情况下,即使拷贝构造函数会产生可观察的副作用,也会对其进行优化,请参见拷贝省略

[edit] 示例

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
}

[edit] 缺陷报告

以下行为更改的缺陷报告已追溯应用于先前发布的 C++ 标准。

DR 应用于 发布的行为 正确的行为
CWG 1353 C++98 隐式声明的拷贝构造函数未定义的条件
未考虑多维数组类型
考虑这些类型
CWG 2094 C++11 易变成员使复制非平凡 (CWG issue 496) 平凡性不受影响
CWG 2171 C++11 X(X&) = default是非平凡的 变为平凡的
CWG 2595 C++20 如果存在另一个约束更多但
未满足其关联约束的拷贝构造函数,则
拷贝构造函数不合格
在这种情况下,它可以是合格的

[edit] 另请参见