命名空间
变体
操作

拷贝构造函数

来自 cppreference.cn
< cpp‎ | 语言
 
 
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)
  • 函数返回:函数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

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

(C++11 起)

[编辑] 已删除的拷贝构造函数

对于类T,隐式声明的或显式默认的(C++11 起)拷贝构造函数在满足以下任何条件时被未定义(C++11 前)定义为已删除(C++11 起)

  • T有一个右值引用类型的非静态数据成员。
(C++11 起)
  • M有一个析构函数,该析构函数已删除或(C++11 起)从拷贝构造函数中不可访问,或
  • 用于查找M的拷贝构造函数的重载决议
  • 未产生可用的候选,或者
  • 在子对象是变体成员的情况下,选择了一个非平凡函数。

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

(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 如果存在另一个拷贝构造函数,它更受约束
但没有满足其关联约束
则拷贝构造函数不合格
它在这种情况下可以合格

[编辑] 参见