首先什么是移动呢,对于拷贝,是重新分配一个内存空间,将原数据复制过去,也就是深层复制。而移动是指,将指针指向同一个内存地址,也就是浅层复制。

移动构造函数的写法:

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.类类型中有移动操作的)此时编译器就能够生成合成的移动操作。