复制赋值运算符

来自cppreference.com
< cpp‎ | language

T 的复制赋值运算符是名为 operator= 的非模板非静态成员函数,它接收恰好一个 TT&const T&volatile T&const volatile T& 类型参数。对于可复制赋值 (CopyAssignable) 类型,它必须有公开的复制赋值运算符。

目录

[编辑] 语法

class_name & class_name :: operator= ( class_name ) (1)
class_name & class_name :: operator= ( const class_name & ) (2)
class_name & class_name :: operator= ( const class_name & ) = default; (3) (C++11 起)
class_name & class_name :: operator= ( const class_name & ) = delete; (4) (C++11 起)

[编辑] 解释

  1. 能使用复制并交换手法时的复制赋值运算符的典型声明。
  2. 不能使用复制并交换手法时(不可交换类型或低性能)的复制赋值运算符的典型声明。
  3. 强制编译器生成复制赋值运算符。
  4. 避免隐式复制赋值。

凡在为重载决议所选择时,复制赋值运算符得到调用,例如对象出现在赋值表达式左侧时。

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

若不对类类型( structclassunion )提供用户定义的复制赋值运算符,则编译器将始终声明它为类的 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=(const T&)T& T::operator=(T)若存在用户定义的复制赋值运算符,则用户仍可用关键词 default 强制编译器生成隐式声明的复制赋值运算符。 (C++11 起)

隐式声明(或在其首声明设为默认)的复制赋值运算符拥有描述于动态异常规定 (C++17 前)异常规定 (C++17 起)的异常规定。

因为复制赋值运算符始终对任何类声明,故基类复制赋值运算符始终被隐藏。若使用 using 声明从基类携带复制赋值运算符,且其参数类型能与导出类的隐式复制赋值运算符的参数类型相同,则 using 声明亦为隐式声明所隐藏。

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

若下列任何为真,则定义类 T 的隐式声明的复制赋值运算符为被删除

  • T 有用户声明的移动构造函数;
  • T 有用户声明的移动赋值运算符。

否则,它被定义为默认化。

若下列任何为真,则定义类 T 的设为默认的复制赋值运算符为被删除

  • T 有非类类型(或其数组)的 const 限定的非静态数据成员;
  • T 有引用类型的非静态数据成员;
  • T 有不能复制赋值的非静态数据成员,或直接或虚基类(复制赋值的重载决议失败,或选择被删除或不可访问的函数);
  • T类联合类,且拥有对应复制赋值运算符为非平凡的变体成员。

[编辑] 平凡复制赋值运算符

若下列全为真,则类 T 的复制赋值运算符为平凡:

  • 它不是用户定义的(表示它是隐式定义或设为默认),且若它被设为默认,则其签名同隐式声明者 (C++14 前)
  • T 无虚成员函数;
  • T 无虚基类;
  • T 的每个直接基类选择的复制赋值运算符是平凡的;
  • T 的每个类类型(或类数组类型)非静态数据成员选择的复制赋值运算符是平凡的;
  • Tvolatile 限定类型的非静态数据成员。
(C++14 起)

平凡复制赋值运算符进行如同用 std::memmove 的对象表示复制。所有与 C 语言兼容的数据类型( POD 类型)是可平凡复制的。

[编辑] 隐式定义的复制赋值运算符

若隐式声明的复制赋值运算符既非被删除亦非平凡,则若它被 odr 使用 ,则为编译器所定义(即生成并编译函数体)。对于 union 类型,隐式定义的复制赋值运算符复制对象表示(如用 std::memmove )。对于非联合类类型( classstruct ),编译器用标量的内建赋值和类类型的复制赋值运算符,以其声明顺序,进行对象基类和非静态成员的逐成员复制。

T 拥有用户定义的析构函数或用户定义的复制赋值运算符,则隐式定义的复制赋值运算符的生成过时。(C++11 起)

[编辑] 注意

若一同提供复制与移动赋值运算符,则若参数为右值(如无名临时量的纯右值或如 std::move 结果的亡值)则重载决议选择移动赋值,若参数为左值(具名对象、返回左值引用的函数/运算符)则选择复制赋值。若仅提供复制赋值,则所有值类别选择它(只要它以值或到 const 引用接收其参数),这使得移动赋值不可用时,复制赋值成为备选。

在继承网格中可通过多于一条路径访问的虚基类,是否会被隐式定义的复制赋值运算符赋值多于一次是未指定的(同样适用于移动赋值)。

用户定义复制赋值运算符的受期待行为的额外细节见赋值运算符重载

[编辑] 示例

#include <iostream>
#include <memory>
#include <string>
#include <algorithm>
 
struct A
{
    int n;
    std::string s1;
    // 用户定义的复制赋值,复制并交换形式
    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(&other == this)
            return *this;
        // 可能时复用存储
        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++ 标准。

DR 应用于 出版时的行为 正确行为
CWG 2171 C++14 operator=(X&) = default 非平凡 令它平凡