命名空间
变体
操作

复制赋值运算符

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

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

目录

[编辑] 语法

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

返回类型 operator=(参数列表 ); (1)
返回类型 operator=(参数列表 ) 函数体 (2)
返回类型 operator=(无默认参数的参数列表 ) = default; (3) (C++11 起)
返回类型 operator=(参数列表 ) = delete; (4) (C++11 起)
返回类型 类名 ::operator=(参数列表 ) 函数体 (5)
返回类型 类名 ::operator=(无默认参数的参数列表 ) = default; (6) (C++11 起)
类名 - 声明其复制赋值运算符的类,在下面的描述中类类型表示为 T
参数列表 - 仅包含一个参数的参数列表,该参数类型为 T, T&, const T&, volatile T&const volatile T&
无默认参数的参数列表 - 仅包含一个参数的参数列表,该参数类型为 T, T&, 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++ 标准。

缺陷报告 应用于 发布时的行为 正确的行为
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 如果存在另一个约束更强但其关联约束不满足的复制赋值运算符,则复制赋值运算符不符合条件
它也可以符合条件
它也可以符合条件
它可以是合格的
在这种情况下

[编辑] 另见