cppreference对移动构造函数的解释
Contact me
- Blog -> https://cugtyt.github.io/blog/index
- Email -> cugtyt@qq.com
- GitHub -> Cugtyt@GitHub
类T的移动构造函数不是模板,它的第一个参数是T&&
,const T&&
,volatile T&&
或const volatile T&&
,或者没有其他参数,或者其他参数有默认值。
语法
class_name ( class_name && ) // (1) (since C++11)
class_name ( class_name && ) = default; // (2) (since C++11)
class_name ( class_name && ) = delete; // (3) (since C++11)
解释
- 移动构造函数的声明
- 强制编译器产生移动构造函数
- 拒绝隐式的移动构造函数
当一个对象从相同类型的[右值(xvalue或prvalue) until C++17,xvalue since C++17]初始化(直接初始化或拷贝初始化)时,移动构造函数被调用,包括:
- 初始化
T a = std::move(b);
或T a(std::move(b));
,其中b的类型是T。 - 函数传参
f(std::move(b));
,其中a的类型是T,f是void f(T t)
。 - 在函数例如
T f()
中返回值return a;
,其中a类型是T,且有移动构造函数。
当初始化prvalue,移动构造函数[通常被优化until C++17,不被调用since C++17],参见拷贝省略。
移动构造函数通常“窃取”参数的资源(例如动态分配对象的指针,文件,TCP,I/O流等),而不是进行拷贝,这导致参数处于可用或者某个中间状态。例如移动std::string
或std::vector
会导致参数不存在,但是这个行为也不确定,比如std::unique_ptr
移动的状态就是确定的。
移动构造函数的隐式声明
如果用户没有对类(struct, class, union)定义移动构造函数,并且以下所有条件为真:
- 没有用户定义的拷贝构造函数
- 没有用户定义的拷贝赋值操作符
- 没有用户定义的移动赋值操作符
- 没有用户定义的析构函数
- 隐式声明的移动构造函数没有被声明为
deleted
until C++14
那么编译器会给类声明一个隐式的inline public
移动构造函数,签名为T::T(T&&)
。
一个类可以有多个移动构造函数,例如T::T(const T&&)
和T::T(T &&)
,如果用户定义了一部分,那么用户任然可以通过default
关键字强制生成移动构造函数。
隐式声明的,或声明为default
的移动构造函数的异常参考[dynamic exception specification until C++17 exception specification since C++17]
不产生隐式声明移动构造函数的情况
类T不产生隐式声明或默认移动构造函数,当以下部分条件为真:
- T有非静态数据成员,且不能被move
- T有直接基类或虚基类不能被move
- T有直接基类或虚基类带有删除的或不可访问的构造函数
- T是像union的类,且有成员存在不平凡的移动构造函数
- T有非静态成员,或直接基类,或虚基类没有移动构造函数,不可以平凡的复制 until C++14
不产生隐式声明移动构造函数会被重载决议忽略 since C++14。
平凡的移动构造函数
以下条件全部为真时,类T的构造函数时平凡的:
- 不是用户提供的(隐式定义的或default)
- 没有虚成员函数
- 没有虚基类
- 每个直接基类T的移动构造函数是平凡的
- 每个非静态成员的移动构造函数是平凡的
- T没有volatile类型的非静态数据成员 since C++14
平凡移动构造函数的行为和普通拷贝函数一样,使用std::memmove
拷贝对象。所有C语言的类型可以平凡移动。
隐式定义的移动构造函数
如果隐式声明的移动构造函数没有被删除也不平凡,编译器会位置定义。对于union
类型,隐式定义的移动构造函数通过std::memmove
拷贝,对于非uunion
的类型(class
或struct
)移动构造函数对所有成员和非静态成员移动。
例子
#include <string>
#include <iostream>
#include <iomanip>
#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) noexcept : s(std::move(o.s)) { }
};
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()); // move-constructs from rvalue temporary
A a2 = std::move(a1); // move-constructs from xvalue
std::cout << "Trying 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 << "Trying to move C\n";
C c1;
C c2 = std::move(c1); // calls copy constructor
std::cout << "Trying to move D\n";
D d1;
D d2 = std::move(d1);
}
output:
Trying to move A
Trying to move B
Before move, b1.s = "test"
After move, b1.s = ""
Trying to move C
move failed!
完
不得不说真的很复杂。。。