命名空间
变体
操作

移动构造函数

来自 cppreference.cn
< cpp‎ | 语言
 
 
C++ 语言
通用主题
流程控制
条件执行语句
if
迭代语句(循环)
跳转语句
函数
函数声明
Lambda 函数表达式
inline 说明符
动态异常规范 (直到 C++17*)
noexcept 说明符 (C++11)
异常
命名空间
类型
说明符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
存储期说明符
初始化
 
 

移动构造函数是一种构造函数,它能以同类类型的右值实参调用,并复制该实参的内容,可能会修改实参。

目录

[编辑] 语法

类名 (形参列表 ); (1)
类名 (形参列表 ) 函数体 (2)
类名 (单形参列表 ) = default; (3)
类名 (形参列表 ) = delete; (4)
类名 ::类名 (形参列表 ) 函数体 (5)
类名 ::类名 (单形参列表 ) = default; (6)
类名 - 要声明其移动构造函数的类
形参列表 - 满足以下所有条件的非空形参列表
  • 给定类类型为 T,首个形参的类型是 T&&const T&&volatile T&&const volatile T&&,并且
  • 要么没有其他形参,要么所有其他形参都有默认实参
单形参列表 - 只含一个形参的形参列表,该形参的类型是 T&&const T&&volatile T&&const volatile T&&,且不带默认实参
函数体 - 移动构造函数的函数体

[编辑] 解释

1) 类定义内的移动构造函数声明。
2-4) 类定义内的移动构造函数定义。
3) 移动构造函数被显式默认。
4) 移动构造函数被删除。
5,6) 类定义外的移动构造函数定义(该类必须包含一个声明 (1))。
6) 移动构造函数被显式默认。
struct X
{
    X(X&& other); // move constructor
//  X(X other);   // Error: incorrect parameter type
};
 
union Y
{
    Y(Y&& other, int num = 1); // move constructor with multiple parameters
//  Y(Y&& other, int num);     // Error: `num` has no default argument
};

移动构造函数通常在对象从同类型的右值(纯右值或亡值)(C++17 前)亡值(C++17 起)初始化(通过直接初始化复制初始化)时调用,包括:

  • 初始化:T a = std::move(b);T a(std::move(b));,其中 b 的类型是 T
  • 函数实参传递:f(std::move(a));,其中 a 的类型是 Tfvoid f(T t)
  • 函数返回:在诸如 T f() 的函数中 return a;,其中 a 的类型是拥有移动构造函数的 T

当初始化器是纯右值时,移动构造函数的调用通常会被优化掉(C++17 前)从不发生(C++17 起),见复制消除

移动构造函数通常“窃取”实参所持有的资源(例如指向动态分配对象的指针、文件描述符、TCP 套接字、线程句柄等),而不是复制它们,并让实参处于某种有效但未指明的状态。因为移动构造函数不改变实参的生存期,所以实参的析构函数通常会在稍后被调用。例如,从 std::stringstd::vector 移动可能会导致实参变为空。对于某些类型,例如 std::unique_ptr,被移动后的状态是完全指定的。

[编辑] 隐式声明的移动构造函数

若没有为类类型提供用户定义的移动构造函数,且以下各项全部为真

那么编译器将声明一个移动构造函数,作为其类的非 explicitinline public 成员,其签名为 T::T(T&&)

一个类可以有多个移动构造函数,例如 T::T(const T&&)T::T(T&&)。如果存在某些用户定义的移动构造函数,用户仍可以使用关键词 default 强制生成隐式声明的移动构造函数。

隐式声明(或在其首次声明时被设为默认)的移动构造函数拥有一个异常规定,如动态异常规定(C++17 前)noexcept 规定(C++17 起)中所述。

[编辑] 隐式定义的移动构造函数

若隐式声明的移动构造函数既未被删除也非平凡,则当它被 ODR 式使用为常量求值所需时,编译器会定义它(即生成并编译一个函数体)。对于联合体类型,隐式定义的移动构造函数复制其对象表示(如同用 std::memmove)。对于非联合体的类类型,移动构造函数以其初始化顺序,对对象的直接基类子对象和成员子对象进行逐成员的移动,这通过以一个亡值实参进行直接初始化来完成。对于每个引用类型的非静态数据成员,移动构造函数将该引用绑定到源引用所绑定的同一对象或函数。

如果这满足constexpr 构造函数(C++23 前)constexpr 函数(C++23 起)的要求,那么生成的移动构造函数是 constexpr 的。

[编辑] 被删除的移动构造函数

T 的隐式声明或显式默认的移动构造函数被定义为已删除的,如果 T 拥有一个类类型 M潜在构造的子对象(或其多维数组),使得:

  • M 有一个已删除的或从复制构造函数不可访问的析构函数,或者
  • 为寻找 M 的移动构造函数而进行的重载决议
  • 未产生可用的候选,或者
  • 在子对象是变体成员的情况下,选择了一个非平凡函数。

这样的构造函数会被重载决议忽略(否则它会阻止从右值进行的复制初始化)。

[编辑] 平凡移动构造函数

T 的移动构造函数是平凡的,如果以下各项全部为真

  • 它不是用户提供的(意即,它是隐式定义或被设为默认的);
  • T 没有虚成员函数;
  • T 没有虚基类;
  • T 的每个直接基类所选择的移动构造函数都是平凡的;
  • T 的每个非静态类类型(或类类型的数组)成员所选择的移动构造函数都是平凡的。

平凡移动构造函数是一种构造函数,它与平凡复制构造函数执行相同的动作,即如同用 std::memmove 一样复制对象表示。所有与 C 语言兼容的数据类型都是可平凡移动的。

[编辑] 合格的移动构造函数

一个移动构造函数如果是合格的,那么它未被删除。

(C++20 前)

一个移动构造函数是合格的,如果满足以下所有条件:

  • 它未被删除。
  • 关联约束(如果有)已满足。
  • 没有其关联约束被满足的移动构造函数比它更受约束
(C++20 起)

合格的移动构造函数的平凡性决定了该类是否为隐式生存期类型,以及该类是否为可平凡复制类型

[编辑] 注意

为使强异常保证成为可能,用户定义的移动构造函数不应抛出异常。例如,std::vector 在需要重分配元素时,依赖 std::move_if_noexcept 来在移动和复制之间做出选择。

若同时提供了复制和移动构造函数,且无其他构造函数可行,则当实参是同类型的右值时(亡值,如 std::move 的结果,或纯右值,如无名临时量(C++17 前)),重载决议会选择移动构造函数;而当实参是左值时(有对象或返回左值引用的函数/运算符),则选择复制构造函数。若只提供了复制构造函数,则所有实参类别都会选择它(只要它接受 const 引用,因为右值可以绑定到 const 引用),这使得在移动不可用时,复制成为移动的备选方案。

[编辑] 示例

#include <iomanip>
#include <iostream>
#include <string>
#include <utility>
 
struct A
{
    std::string s;
    int k;
 
    A() : s("test"), k(-1) {}
    A(const A& o) : s(o.s), k(o.k) { std::cout << "move failed!\n"; }
    A(A&& o) noexcept :
        s(std::move(o.s)),       // explicit move of a member of class type
        k(std::exchange(o.k, 0)) // explicit move of a member of non-class type
    {}
};
 
A f(A a)
{
    return a;
}
 
struct B : A
{
    std::string s2;
    int n;
    // implicit move constructor B::(B&&)
    // calls A's move constructor
    // calls s2's move constructor
    // and makes a bitwise copy of n
};
 
struct C : B
{
    ~C() {} // destructor prevents implicit move constructor C::(C&&)
};
 
struct D : B
{
    D() {}
    ~D() {}           // destructor would prevent implicit move constructor D::(D&&)
    D(D&&) = default; // forces a move constructor anyway
};
 
int main()
{
    std::cout << "Trying to move A\n";
    A a1 = f(A()); // return by value move-constructs the target
                   // from the function parameter
 
    std::cout << "Before move, a1.s = " << std::quoted(a1.s)
        << " a1.k = " << a1.k << '\n';
 
    A a2 = std::move(a1); // move-constructs from xvalue
    std::cout << "After move, a1.s = " << std::quoted(a1.s)
        << " a1.k = " << a1.k << '\n';
 
 
    std::cout << "\nTrying to move B\n";
    B b1;
 
    std::cout << "Before move, b1.s = " << std::quoted(b1.s) << "\n";
 
    B b2 = std::move(b1); // calls implicit move constructor
    std::cout << "After move, b1.s = " << std::quoted(b1.s) << "\n";
 
 
    std::cout << "\nTrying to move C\n";
    C c1;
    C c2 = std::move(c1); // calls copy constructor
 
    std::cout << "\nTrying to move D\n";
    D d1;
    D d2 = std::move(d1);
}

输出

Trying to move A
Before move, a1.s = "test" a1.k = -1
After move, a1.s = "" a1.k = 0
 
Trying to move B
Before move, b1.s = "test"
After move, b1.s = ""
 
Trying to move C
move failed!
 
Trying to move D

[编辑] 缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

缺陷报告 应用于 发布时的行为 正确的行为
CWG 1353 C++11 默认移动构造函数被定义为
已删除的条件没有考虑多维数组类型
考虑这些类型
CWG 1402 C++11 会调用非平凡复制构造函数的
默认移动构造函数被定义为已删除;
一个被删除的默认移动构造函数
仍会参与重载决议
允许调用这样的复制
构造函数;使其被重载
决议忽略
CWG 1491 C++11 带有右值引用类型非静态数据成员的
类的默认移动构造函数被定义为已删除
在这种情况下不被删除
CWG 2094 C++11 一个 volatile 子对象使得一个默认的
移动构造函数变为非平凡(CWG 问题 496
平凡性不受影响
CWG 2595 C++20 如果存在另一个更受约束的移动构造函数,
那么一个移动构造函数就不合格
则拷贝构造函数不合格
它在这种情况下可以合格

[编辑] 参阅