C++ Primer第五版笔记——移动操作(一)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/rest_in_peace/article/details/81483224

新标准下的一个重要特性就是可以移动而非拷贝对象的能力,比如在标准库容器中,重新分配内存的过程中,从旧内存将元素拷贝到新内存中是不必要的,更好的方式是移动元素;另一个原因是因为IO类或unique_ptr这样的类是包含不能被共享的资源的,不能使用拷贝只能使用移动。
右值引用:
右值引用使用“&&”来获得,其只能绑定到即将被销毁的对象,因此,可以自由的将一个右值引用的资源“移动“到另一个对象中
左值引用与右值引用的区别:
返回左值引用的函数,连同赋值、下标、解引用和前置递增递减运算符,都是返回左值的表达式的例子;
返回右值引用的函数,连同算术、关系、位运算和后置递增递减运算符,都是返回右值的表达式的例子,但是一个const的左值引用也可以绑定到这类表达式上;
左值有持久的状态,但右值要么是字面常量,要么是在表达式求值过程中创建的临时变量;
变量时左值,毕竟变量时持久的,知道离开作用域才会被销毁。
标准库move函数:
可以显示的将一个左值转换为相对应的右值引用类型,还可以通过调用move函数来获得绑定到左值的右值引用,此函数定义在头文件utility中:

int &&r2 = r1;          //错误,表达式r1是左值
int &&r2 = std::move(r1);   //正确

move告诉编译器:我们有一个左值,但是我们希望像一个右值一样去使用它,这表示除了对r1进行重新赋值和销毁之外,不能再对它的值进行别的使用。

移动构造函数和移动赋值运算符:
类似拷贝构造函数,移动构造函数的第一个参数是该类类型的一个引用,但是该引用是一个右值引用,与拷贝构造函数一样,另外的参数都应该有默认实参。
除了完成资源的移动,移动构造函数还必须确保移动后的源对象处于一个这样的状态:销毁它是无害的。一旦资源移动完成,源对象必须不再指向被移动的资源,这些资源的归属权已经归新的对象所有。

class A{
public:
    A(A&& a) noexcept             //移动操作不应抛出任何异常
        //成员初始化器接管s中的资源
        :i(a.i){
        //令s进入销毁无害状态
        s.i = nullptr;
    }
private:
    int* i;
};

移动构造函数不分配任何新内存,它接管给定的对象的内存,之后将接管对象的指针都赋值为空。
移动操作,标准库容器和异常:
移动操作通常不分配内存,因此不会抛出异常,当编写一个不抛出异常的移动操作时 ,我们应当将此事通知标准库,避免额外工作,可以通过指定”noexcept“来做到。
需要指出不抛出异常的原因是:首先,虽然移动操作通常不抛出异常,但是抛出异常也是允许的;其次,标准容器能对异常发生时其自身的行为提供保障。这是因为使用移动操作时如果在过程中抛出了异常,旧空间的移动源元素已经被改变,而新空间中的元素可能尚不存在,无法对自身提供保障;另外使用拷贝构造函数就能很容易满足要求,因此除非标准库知道移动构造函数不抛出异常,否则在重新分配内存的过程中,他就必须使用拷贝构造函数,所以必须显示告诉标准库我们的移动构造函数是能安全使用的。

移动赋值运算符:
移动赋值运算符也需要标记为”noexcept“,且需要正确处理是不是参数是否为自身:

//假设有类A,有属性i
A& A::operator=(A&& rhs) noexcpet{
    //避免传入参数为自身的误处理
    if(this != &rhs){
        free();                     //释放已有元素
        i = rhs.i;                  //接管资源
        //将rhs设为可析构状态
        rhs.i = nullptr;
    }
    return this;
} 

在执行完移动操作后,源对象必须是可析构的,还必须是有效的,即可以安全的为它赋新值或者可以安全的使用而不依赖其当前值。
合成的移动操作:
与处理拷贝构造函数和拷贝赋值运算符一样,编译器也会合成移动构造函数和移动赋值运算符。但是编译器也不会为某些类合成移动操作,特别是,如果一个类定义了自己的拷贝构造函数、拷贝赋值运算符或析构函数,编译器就不会为它合成移动构造函数和移动赋值运算符了。

猜你喜欢

转载自blog.csdn.net/rest_in_peace/article/details/81483224