Copy assignment operator
复制赋值运算符是一个非模板 非静态成员函数 ,其名称为 operator = ,可通过相同类类型的参数调用,并在不改变参数内容的情况下复制参数内容。
目录 |
语法
关于正式的复制赋值运算符语法,请参阅 函数声明 。以下语法列表仅展示了所有有效复制赋值运算符语法的一部分。
返回类型
operator=(
参数列表
);
|
(1) | ||||||||
返回类型
operator=(
参数列表
)
函数体
|
(2) | ||||||||
返回类型
operator=(
无默认参数列表
) = default;
|
(3) | (C++11 起) | |||||||
返回类型
operator=(
参数列表
) = delete;
|
(4) | (C++11 起) | |||||||
返回类型
类名
::
operator=(
参数列表
)
函数体
|
(5) | ||||||||
返回类型
类名
::
operator=(
无默认参数列表
) = default;
|
(6) | (C++11 起) | |||||||
| class-name | - |
正在声明拷贝赋值运算符的类,在以下描述中类类型以
T
表示
|
| parameter-list | - |
仅包含一个参数的
参数列表
,该参数类型为
T
,
T&
,
const
T
&
,
volatile
T
&
或
const
volatile
T
&
|
| parameter-list-no-default | - |
仅包含一个参数的
参数列表
,该参数类型为
T
,
T&
,
const
T
&
,
volatile
T
&
或
const
volatile
T
&
且没有默认参数
|
| function-body | - | 拷贝赋值运算符的 函数体 |
| return-type | - |
任意类型,但推荐使用
T&
以支持赋值链式操作
|
说明
struct X { X& operator=(X& other); // 拷贝赋值运算符 X operator=(X other); // 允许按值传递 // X operator=(const X other); // 错误:参数类型不正确 }; union Y { // 拷贝赋值运算符可以采用上述未列出的语法形式, // 只要遵循通用函数声明语法 // 且不违反上述列出的限制条件 auto operator=(Y& other) -> Y&; // 正确:使用尾置返回类型 Y& operator=(this Y& self, Y& other); // 正确:使用显式对象参数 // Y& operator=(Y&, int num = 1); // 错误:存在其他非对象参数 };
复制赋值运算符在 重载决议 选中时被调用,例如当对象出现在赋值表达式左侧时。
隐式声明的复制赋值运算符
如果类类型没有提供用户定义的复制赋值运算符,编译器将始终将其声明为类的 inline public 成员。此隐式声明的复制赋值运算符具有 T & T :: operator = ( const T & ) 的形式,当满足以下所有条件时:
-
T的每个直接基类B都具有复制赋值运算符,其参数为B或 const B & 或 const volatile B & ; -
T的每个类类型或类类型数组的非静态数据成员M都具有复制赋值运算符,其参数为M或 const M & 或 const volatile M & 。
否则隐式声明的复制赋值运算符被声明为 T & T :: operator = ( T & ) 。
由于这些规则,隐式声明的复制赋值运算符无法绑定到 volatile 左值参数。
一个类可以拥有多个复制赋值运算符,例如同时存在 T & T :: operator = ( T & ) 和 T & T :: operator = ( T ) 。 如果存在用户定义的复制赋值运算符,用户仍可通过 default 关键字强制生成隐式声明的复制赋值运算符。 (C++11 起)
隐式声明(或在首次声明时默认生成)的复制赋值运算符具有异常规范,具体描述见 动态异常规范 (直至 C++17) noexcept 规范 (自 C++17 起)
由于复制赋值运算符始终为任何类声明,基类的赋值运算符始终被隐藏。如果使用 using声明 从基类引入赋值运算符,且其参数类型可能与派生类隐式赋值运算符的参数类型相同,则该using声明也会被隐式声明所隐藏。
隐式定义的复制赋值运算符
若隐式声明的复制赋值运算符既未被删除也非平凡,则当 被ODR使用 或 常量求值所需时 (C++14起) ,编译器会定义该运算符(即生成并编译函数体)。对于联合体类型,隐式定义的复制赋值会复制对象表示(如同通过 std::memmove )。对于非联合类类型,该运算符按初始化顺序对对象的直接基类和静态数据成员执行逐成员复制赋值:对标量类型使用内建赋值,对数组类型使用逐元素复制赋值,对类类型调用复制赋值运算符(非虚调用)。
|
类
|
(C++14 起)
(C++23 前) |
|
类
|
(C++23 起) |
|
如果
|
(since C++11) |
已删除的复制赋值运算符
对于类
T
的隐式声明
或显式默认
(C++11 起)
的复制赋值运算符,若满足以下任意条件,则该运算符被
未定义
(C++11 前)
定义为弃置
(C++11 起)
:
-
T拥有一个 const 限定非类类型(或可能是其多维数组)的非静态数据成员。 -
T拥有一个引用类型的非静态数据成员。 -
T拥有一个类类型M(或可能是其多维数组)的 潜在构造子对象 ,该情况适用于查找M的拷贝赋值运算符时进行的重载解析
-
- 不会产生可用的候选函数,或
- 当子对象为 variant member 时,选择了非平凡函数。
| (since C++11) |
平凡复制赋值运算符
类
T
的复制赋值运算符在满足以下所有条件时为平凡:
- 它不是用户提供的(即它是隐式定义或默认的);
-
T没有虚成员函数; -
T没有虚基类; -
为
T的每个直接基类选择的复制赋值运算符是平凡的; -
为
T的每个非静态类类型(或类类型数组)成员选择的复制赋值运算符是平凡的。
一个平凡的复制赋值运算符会像通过 std::memmove 那样复制对象表示。所有与C语言兼容的数据类型(POD类型)都是可平凡复制赋值的。
合格的复制赋值运算符
|
当复制赋值运算符满足以下条件之一时即为合格:由用户声明,或同时满足隐式声明且可定义。 |
(C++11 前) |
|
当复制赋值运算符未被删除时即为合格。 |
(C++11 起)
(C++20 前) |
|
当复制赋值运算符满足以下全部条件时即为合格: |
(C++20 起) |
合格的复制赋值运算符的平凡性决定了该类是否为 平凡可复制类型 。
注释
如果同时提供了复制赋值和移动赋值运算符,重载解析会在实参为 右值 (如无名临时量的 纯右值 或如 std::move 结果的 亡值 )时选择移动赋值,在实参为 左值 (具名对象或返回左值引用的函数/运算符)时选择复制赋值。若仅提供复制赋值,则所有实参类别都会选择它(只要其参数按值传递或作为const引用传递,因为右值可以绑定到const引用),这使得复制赋值成为移动赋值不可用时的后备方案。
对于在继承结构中可通过多条路径访问的虚基类子对象,隐式定义的复制赋值运算符是否会多次对其进行赋值是未指定的(同样适用于 移动赋值 运算符)。
请参阅 赋值运算符重载 以获取关于用户定义拷贝赋值运算符预期行为的更多细节。
示例
#include <algorithm> #include <iostream> #include <memory> #include <string> struct A { int n; std::string s1; A() = default; A(A const&) = default; // 用户定义的拷贝赋值(拷贝并交换惯用法) A& operator=(A other) { std::cout << "copy assignment of A\n"; std::swap(n, other.n); std::swap(s1, other.s1); return *this; } }; struct B : A { std::string s2; // 隐式定义的拷贝赋值 }; struct C { std::unique_ptr<int[]> data; std::size_t size; // 用户定义的拷贝赋值(非拷贝并交换惯用法) // 注意:拷贝并交换会始终重新分配资源 C& operator=(const C& other) { if (this != &other) // 非自赋值 { if (size != other.size) // 资源无法复用 { data.reset(new int[other.size]); size = other.size; } std::copy(&other.data[0], &other.data[0] + size, &data[0]); } return *this; } }; int main() { A a1, a2; std::cout << "a1 = a2 calls "; a1 = a2; // 用户定义的拷贝赋值 B b1, b2; b2.s1 = "foo"; b2.s2 = "bar"; std::cout << "b1 = b2 calls "; b1 = b2; // 隐式定义的拷贝赋值 std::cout << "b1.s1 = " << b1.s1 << "; b1.s2 = " << b1.s2 << '\n'; }
输出:
a1 = a2 calls copy assignment of A b1 = b2 calls copy assignment of A b1.s1 = foo; b1.s2 = bar
缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的C++标准。
| 缺陷报告 | 应用于 | 发布时的行为 | 正确行为 |
|---|---|---|---|
| CWG 1353 | C++98 |
隐式声明的拷贝赋值运算符未定义的情况
未考虑多维数组类型 |
考虑这些类型 |
| CWG 2094 | C++11 |
volatile 子对象使默认化的拷贝赋值运算符
变为非平凡( CWG 496号问题 ) |
不影响平凡性 |
| CWG 2171 | C++11 | operator = ( X & ) = default 是非平凡的 | 改为平凡 |
| CWG 2180 | C++11 |
类
T
的默认化拷贝赋值运算符未被定义为删除的,
当
T
是抽象类且具有不可拷贝赋值的直接虚基类时
|
在此情况下该运算符
被定义为删除的 |
| CWG 2595 | C++20 |
当存在另一个更具约束但不满足其关联约束的
拷贝赋值运算符时,当前拷贝赋值运算符不符合条件 |
在此情况下
可以符合条件 |