移动赋值运算符

来自cppreference.com
< cpp‎ | language

T 的移动赋值运算符是名为 operator= 并正好接受一个 T&&const T&&volatile T&&const volatile T&& 类型参数的非模板非静态成员函数。

目录

[编辑] 语法

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

[编辑] 解释

  1. 移动赋值运算符的典型声明。
  2. 强制编译器生成移动赋值运算符。
  3. 避免隐式移动赋值。

移动赋值运算符在凡为重载决议所选择时得到调用,例如当对象出现在赋值表达式左侧,而其中右侧运算数是同类型右值或可隐式转换的类型。

典型的移动赋值运算符“窃取”参数所保有的资源(例如指向动态分配对象的指针、文件描述符、 TCP 接头、 I/O 流、运行的线程等),而非复制它们,并使得参数留在合法但不确定的状态。例如从 std::string 或从 std::vector 移动赋值可能导致参数被置空。然而此非保证。移动赋值的定义与普通赋值相比更不严格,而非更严格;在完成时,普通赋值必须至少留下数据的二个副本,而移动赋值只要求留下一份。

[编辑] 隐式声明的移动赋值运算符

若不对类类型( structclassunion )提供用户定义的移动赋值运算符,且下列全为真:

  • 无用户声明的复制构造函数;
  • 无用户声明的移动构造函数;
  • 无用户声明的复制赋值运算符;
  • 无用户声明的析构函数;
  • 隐式声明的移动赋值运算符不会定义为被删除,
(C++14 前)

则编译器将声明移动赋值运算符为其类的 inline public 成员,并拥有签名 T& T::operator=(T&&)

类能拥有多个移动赋值运算符,例如 T& T::operator=(const T&&)T& T::operator=(T&&) 。若存在用户定义的移动赋值运算符,则用户仍可用关键词 default 强制编译器生成隐式声明的移动赋值运算符。

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

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

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

若下列任何为真,则定义类 T 的隐式声明或设为默认的移动赋值运算符为被删除

  • Tconst 限定的非静态数据成员;
  • T 有引用类型的非静态数据成员;
  • T 有不能移动赋值(有被删除、不可访问或歧义的移动赋值运算符)的非静态数据成员;
  • T 有不能移动赋值(有被删除、不可访问或歧义的移动赋值运算符)的直接或虚基类;
  • T 有无移动赋值运算符而不可平凡复制的非静态数据成员或直接或虚基类。
(C++14 前)

被删除的隐式声明的移动赋值运算符为重载决议所忽略。

(C++14 起)

[编辑] 平凡移动赋值运算符

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

  • 它非用户提供(表示是隐式定义或设为默认);
  • T 无虚成员函数;
  • T 无虚基类;
  • T 的每个直接基类选择的移动赋值运算符为平凡;
  • T 的每个类类型(或类数组数组)非静态数据成员选择的移动赋值运算符为平凡;
  • Tvolatile 限定类型的非静态数据成员。
(C++14 起)

平凡移动赋值运算符所进行的动作同平凡复制赋值运算符,即如同以 std::memmove 复制对象表示。所有与 C 兼容的数据类型( POD 类型)均为可平凡移动赋值。

[编辑] 隐式定义的移动赋值运算符

若隐式声明的移动赋值运算符既非被删除亦非平凡,则若它被 odr 使用 ,则为编译器所定义(即生成并编译函数体)。

对于 union 类型,隐式定义的移动赋值运算符复制对象表示(如用 std::memmove )。

对于非联合类类型( classstruct ),移动赋值运算符对标量用内建运算符,对数组用逐元素移动赋值,而对类类型用移动赋值运算符(非虚调用),以其声明顺序,进行该对象直接基类和间接非静态成员的全逐成员移动赋值。

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

struct V
{
    V& operator=(V&& other) {
        // 这可能被调用一或二次
        // 若调用二次,则 'other' 是刚被移动的 V 子对象
        return *this;
    }
};
struct A : virtual V { }; // operator= 调用 V::operator=
struct B : virtual V { }; // operator= 调用 V::operator=
struct C : B, A { };      // operator= 调用 B::operator= ,然后调用 A::operator=
                          // 但可能只调用一次 V::operator=
 
int main()
{
  C c1, c2;
  c2 = std::move(c1);
}
(C++14 起)

[编辑] 注意

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

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

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

[编辑] 示例

#include <string>
#include <iostream>
#include <utility>
 
struct A
{
    std::string s;
    A() : s("test") { }
    A(const A& o) : s(o.s) { std::cout << "move failed!\n"; }
    A(A&& o) : s(std::move(o.s)) { }
    A& operator=(const A& other)
    {
         s = other.s;
         std::cout << "copy assigned\n";
         return *this;
    }
    A& operator=(A&& other)
    {
         s = std::move(other.s);
         std::cout << "move assigned\n";
         return *this;
    }
};
 
A f(A a) { return a; }
 
struct B : A
{
     std::string s2; 
     int n;
     // 隐式移动赋值运算符 B& B::operator=(B&&)
     // 调用 A 的移动赋值运算符
     // 调用 s2 的移动赋值运算符
     // 并进行 n 的逐位复制
};
 
struct C : B
{
    ~C() { } // 析构函数阻止隐式移动赋值
};
 
struct D : B
{
    D() { }
    ~D() { } // 析构函数本会阻止隐式移动赋值
    D& operator=(D&&) = default; // 无论如何都强制移动赋值
};
 
int main()
{
    A a1, a2;
    std::cout << "Trying to move-assign A from rvalue temporary\n";
    a1 = f(A()); // 从右值临时量移动赋值
    std::cout << "Trying to move-assign A from xvalue\n";
    a2 = std::move(a1); // 从亡值移动赋值
 
    std::cout << "Trying to move-assign B\n";
    B b1, b2;
    std::cout << "Before move, b1.s = \"" << b1.s << "\"\n";
    b2 = std::move(b1); // 调用隐式移动赋值
    std::cout << "After move, b1.s = \"" << b1.s << "\"\n";
 
    std::cout << "Trying to move-assign C\n";
    C c1, c2;
    c2 = std::move(c1); // 调用复制赋值运算符
 
    std::cout << "Trying to move-assign D\n";
    D d1, d2;
    d2 = std::move(d1);
}

输出:

Trying to move-assign A from rvalue temporary
move assigned
Trying to move-assign A from xvalue
move assigned
Trying to move-assign B
Before move, b1.s = "test"
move assigned
After move, b1.s = "" 
Trying to move-assign C
copy assigned
Trying to move-assign D
move assigned