命名空间
变体
操作

复制赋值运算符

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

复制赋值运算符是一个非模板 非静态成员函数,其名称为 operator=,可以使用相同类类型的参数调用,并在不改变参数内容的情况下复制参数的内容。

目录

[编辑] 语法

有关正式的复制赋值运算符语法,请参见 函数声明。下面的语法列表仅演示所有有效复制赋值运算符语法的一个子集。

返回类型 operator=(参数列表 ); (1)
返回类型 operator=(参数列表 ) 函数体 (2)
返回类型 operator=(无默认值的参数列表 ) = default; (3) (自 C++11 起)
返回类型 operator=(参数列表 ) = delete; (4) (自 C++11 起)
返回类型 类名 ::operator=(参数列表 ) 函数体 (5)
返回类型 类名 ::operator=(无默认值的参数列表 ) = default; (6) (自 C++11 起)
类名 - 正在声明其复制赋值运算符的类,在以下描述中,类类型以 T 给出
参数列表 - 只有一个参数的参数列表,其类型为 TT&const T&volatile T&const volatile T&
无默认值的参数列表 - 只有一个参数的参数列表,其类型为 TT&const T&volatile T&const volatile T&,并且没有默认参数
函数体 - 复制赋值运算符的函数体
返回类型 - 任何类型,但为了允许赋值链,倾向于使用 T&

[编辑] 解释

1) 类定义内部的复制赋值运算符的声明。
2-4) 类定义内部的复制赋值运算符的定义。
3) 复制赋值运算符被显式默认化。
4) 复制赋值运算符被删除。
5,6) 类定义外部的复制赋值运算符的定义(类必须包含声明 (1))。
6) 复制赋值运算符被显式默认化。
struct X
{
    X& operator=(X& other);     // copy assignment operator
    X operator=(X other);       // pass-by-value is allowed
//  X operator=(const X other); // Error: incorrect parameter type
};
 
union Y
{
    // copy assignment operators can have syntaxes not listed above,
    // as long as they follow the general function declaration syntax
    // and do not viloate the restrictions listed above
    auto operator=(Y& other) -> Y&;       // OK: trailing return type
    Y& operator=(this Y& self, Y& other); // OK: explicit object parameter
//  Y& operator=(Y&, int num = 1);        // Error: has other non-object parameters
};

当通过重载决议选择复制赋值运算符时,例如当对象出现在赋值表达式的左侧时,会调用复制赋值运算符。

[编辑] 隐式声明的复制赋值运算符

如果未为类类型提供用户定义的复制赋值运算符,则编译器将始终声明一个作为该类的 inline public 成员。如果以下所有条件都为真,则此隐式声明的复制赋值运算符具有形式 T& T::operator=(const T&)

  • T 的每个直接基类 B 都有一个复制赋值运算符,其参数为 Bconst B&const volatile B&
  • 类类型或类类型数组的 T 的每个非静态数据成员 M 都有一个复制赋值运算符,其参数为 Mconst 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)。对于非联合类类型,运算符按初始化顺序对对象的直接基类和非静态数据成员执行逐成员复制赋值,标量使用内置赋值,数组使用逐成员复制赋值,类类型使用复制赋值运算符(非虚调用)。

T 的隐式定义复制赋值运算符是 constexpr,如果

  • T 是一个 字面类型,并且
  • 为复制每个直接基类子对象而选择的赋值运算符是一个 constexpr 函数,并且
  • 对于 T 的每个类类型(或其数组)的非静态数据成员,为复制该成员而选择的赋值运算符是一个 constexpr 函数。
(自 C++14 起)
(直到 C++23)

T 的隐式定义复制赋值运算符是 constexpr

(自 C++23 起)


如果 T 具有用户声明的析构函数或用户声明的复制构造函数,则不推荐使用隐式定义的复制赋值运算符的生成。

(自 C++11 起)

[编辑] 已删除的复制赋值运算符

T 的隐式声明或显式默认化(自 C++11 起)复制赋值运算符是 未定义的(直到 C++11)定义为已删除的(自 C++11 起),如果满足以下任何条件

  • T 具有 const 限定的非类类型(或可能是多维数组)的非静态数据成员。
  • T 具有引用类型的非静态数据成员。
  • T 具有类类型 M(或可能是多维数组)的潜在构造的子对象,使得应用于查找 M 的复制赋值运算符的重载决议
  • 未产生可用的候选者,或者
  • 在子对象是变体成员的情况下,选择了一个非平凡的函数。

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

(自 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;
 
    // user-defined copy assignment (copy-and-swap idiom)
    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;
    // implicitly-defined copy assignment
};
 
struct C
{
    std::unique_ptr<int[]> data;
    std::size_t size;
 
    // user-defined copy assignment (non copy-and-swap idiom)
    // note: copy-and-swap would always reallocate resources
    C& operator=(const C& other)
    {
        if (this != &other) // not a self-assignment
        {
            if (size != other.size) // resource cannot be reused
            {
                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; // user-defined copy assignment
 
    B b1, b2;
    b2.s1 = "foo";
    b2.s2 = "bar";
    std::cout << "b1 = b2 calls ";
    b1 = b2; // implicitly-defined copy assignment
 
    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++ 标准。

DR 应用于 已发布行为 正确行为
CWG 1353 C++98 隐式声明的复制赋值运算符的条件
未考虑多维数组类型是未定义的
考虑这些类型
CWG 2094 C++11 volatile 子对象使默认复制
赋值运算符变为非平凡的 (CWG issue 496)
平凡性不受影响
CWG 2171 C++11 operator=(X&) = default 是非平凡的 变为平凡的
CWG 2180 C++11 T 的默认复制赋值运算符未定义为已删除
如果 T 是抽象类且具有不可复制赋值的直接虚基类
运算符被定义
在这种情况下被删除
CWG 2595 C++20 如果存在另一个复制赋值运算符,则复制赋值运算符不合格
更受约束但未满足其关联约束
它可以是合格的
在这种情况下
在这种情况下

[编辑] 参见