首先什么是移动呢,对于拷贝,是重新分配一个内存空间,将原数据复制过去,也就是深层复制。而移动是指,将指针指向同一个内存地址,也就是浅层复制。
移动构造函数的写法:
class A
{
public:
A(A &&temp):val(temp.val),a(temp.a)//成员指针指向同一块区域实现移动
{
temp.a = nullptr;//原先的指针与内存空间断开联系,防止因调用析构函数或者之后的误用破坏数据
}
private:
int *a;
int val;
};
类似于以下这种函数:
A GetA()
{
A temp;
return A;
}
int main()
{
A a = GetA();
}
如果没有移动构造函数的情况下,因为temp是函数中的局部变量,返回时需要系统定义一个类A的临时对象,将temp复制过去后,再离开函数,销毁temp。然后执行A的拷贝构造函数将临时对象的值拷贝给a,再销毁临时对象。
但是重新分配内存空间再生成临时对象是需要消耗的。如果加入移动构造函数后,临时对象就直接指向了原来的空间,无需重新开辟内存。
记住,移动构造函数中一定要将原来的指针断开,避免两次触发析构函数,释放同一块区域两次。
对于类中有动态分配内存的指针的情况:
普通的拷贝构造函数和移动赋值运算符的区别:
class A
{
public:
A &operator=(A &temp)//拷贝赋值运算符
{
val = temp.val;
delete p;
p = new int;
*p = *temp.p;
return *this;
}
A &operator=(A &&temp) noexcept//移动赋值运算符
{
val = temp.val;
delete p;
p = temp.p;
temp.p = nullptr;
return *this;
}
private:
int *p;
int val;
};
noexcept关键字的作用是告诉编译器此函数不会出现异常抛出,可以阻止编译器做不必要的准备。由于移动拷贝是浅层复制,没有重新动态分配内存空间,所以也不会有异常出现。
a)当类中有自己定义的拷贝构造函数或拷贝赋值运算符或析构函数时,则编译器不会自动生成合成的移动构造函数及运算符。
b)如果没有自己定义移动操作,且又没有合成的移动操作时,当A b;A a = move(b)时,会自动调用拷贝操作。即使对象是右值。
c)只有一个类中没有定义拷贝操作,析构函数。且成员都是可移动的时候(1.内置类型可移动2.类类型中有移动操作的)此时编译器就能够生成合成的移动操作。