命名空间
变体
操作

复制赋值运算符

来自 cppreference.com
< 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具有常量限定的非类类型(或可能的多维数组)的非静态数据成员。
  • 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 易失性子对象使默认复制
赋值运算符非平凡 (CWG 问题 496)
平凡性不受影响
CWG 2171 C++11 operator=(X&) = default 非平凡 变得平凡
CWG 2180 C++11 T的默认复制赋值运算符未定义为已删除
如果T是抽象的并且具有不可复制赋值的直接虚基类
运算符被定义
在这种情况下,定义为已删除
CWG 2595 C++20 如果存在另一个
更受约束但无法满足其关联约束的复制赋值运算符,则复制赋值运算符不合格
在这种情况下,它可以是合格的
它可以是合格的
在这种情况下

[编辑] 另请参阅