详解C++11中的右值引用与移动语义
C++11的一个最主要的特性就是可以移动而非拷贝对象的能力。很多情况都会发生对象的拷贝,有时对象拷贝后就立即销毁,在这些情况下,移动而非拷贝对象会大幅度提升性能。
右值与右值引用
为了支持移动操作,新标准引入了一种新的引用类型——右值引用,就是必须绑定到右值的引用。我们通过&&而不是&来获得右值引用。右值引用一个重要的特性就是只能绑定到将要销毁的对象。
左值和右值是表达式的属性,一些表达式生成或要求左值,而另一些则生成或要求右值。一般而言,一个左值表达式表示的是一个对象的身份,而右值表达式表示的是对象的值。(可以取地址的、有名字的就是左值;不能取地址的、没有名字的就是右值。)两者明显的区别就是左值有持久的状态,而右值要么是字面常量,要么是在表达式求值过程中创建的临时对象。
类似于常规引用(左值引用),一个右值引用也不过是某个对象的另一个名字而已。我们不能将左值引用绑定到要求转换的表达式、字面常量或是返回值的表达式,也不能把右值应用直接绑定到一个左值上。但是,常量左值引用可以绑定到非常量左值、常量左值、右值,是一个万能引用类型。不过相比右值引用所引用的右值,常量左值引用所引用的右值在它的“余生”中只能是只读的。
int i = 42; int &r = i; //r引用i int &r2 = i*2; //错误,i*2是一个右值 int &&rr = i; //错误,不能将一个右值引用绑定到一个左值上 int &&rr2 = i*2; //正确,将rr2绑定到一个乘法结果上 const int &r3 = i*2; //正确,将一个常量引用绑定到一个右值上
变量可以看做只有一个运算对象而没有运算符的表达式,是一个左值。我们不能将一个右值引用直接绑定到一个变量上,即使这个变量是右值引用类型。但是,我们可以通过新标准库中的move函数来获得绑定到左值上的右值引用。
int &&rr3 = std::move(rr2);
注意,被转化的左值,其生命周期并没有随着左右至的转化而改变,在转换之后使用左值可能造成运行时错误。因此,调用move就意味着承诺:除了对原左值变量赋值或销毁它外,我们将不再使用它。不过更多的时候,我们需要转换成右值引用的还是一个确实生命周期即将结束的对象。
移动构造函数和移动赋值运算符
为了让自定义类型也支持移动操作,需要为其定义移动构造函数和移动赋值运算符。这两个成员类似对应的拷贝操作,但它们从给定对象窃取资源而不是拷贝资源。类似于拷贝构造函数,移动构造函数的第一个参数是该类类型的一个右值引用,任何额外的参数都必须有默认实参。除了完成资源移动外,移动构造函数还必须确保移后源对象处于有效的、可析构的状态。
#include <iostream> #include <algorithm> class MemoryBlock { public: // 构造函数 explicit MemoryBlock(size_t length) : _length(length) , _data(new int[length]) {} // 析构函数 ~MemoryBlock() { if (_data != nullptr) delete[] _data; } // 拷贝赋值运算符 MemoryBlock& operator=(const MemoryBlock& other) { if (this != &other) { delete[] _data; _length = other._length; _data = new int[_length]; std::copy(other._data, other._data + _length, _data); } return *this; } // 拷贝构造函数 MemoryBlock(const MemoryBlock& other) : _length(0) , _data(nullptr) { *this = other; } // 移动赋值运算符,通知标准库该构造函数不抛出任何异常(如果抛出异常会怎么样?) MemoryBlock& operator=(MemoryBlock&& other) noexcept { if (this != &other) { delete[] _data; // 移动资源 _data = other._data; _length = other._length; // 使移后源对象处于可销毁状态 other._data = nullptr; other._length = 0; } return *this; } // 移动构造函数 MemoryBlock(MemoryBlock&& other) noexcept _data(nullptr) , _length(0) { *this = std::move(other); } size_t Length() const { return _length; } private: size_t _length; // The length of the resource. int* _data; // The resource. };
只有当一个类没有定义任何自己版本的拷贝控制成员,且类的每个非static数据成员都可移动时,编译器才会为它合成移动构造函数会移动赋值运算符。编译器可以移动内置类型;如果一个类类型有对应的移动操作,编译器也能移动这个类型的成员。此外,定义了一个移动构造函数或移动赋值运算符的类必须也定义自己的拷贝操作;否则,这些成员默认地定义为删除的。而移动操作则不同,它永远不会隐式定义为删除的。但如果我们显式地要求编译器生成=defualt的移动操作,且编译器不能移动所有成员,则编译器会将移动操作定义为删除的函数。
如果一个类既有移动构造函数又有拷贝构造函数,编译会使用普通的函数匹配规则来确定使用哪个构造函数。但如果只定义了拷贝操作而未定义移动操作,编译器不会合成移动构造函数,此时即使调用move来移动它们,也是调用的拷贝操作。
class Foo{ public: Foo() = default; Foo(const Foo&); // 为定义移动构造函数 }; Foo x; Foo y(x); //调用拷贝构造函数 Foo z(std::move(x)); //调用拷贝构造函数,因为未定义移动构造函数
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。
您可能感兴趣的文章
- 01-10求子数组最大和的解决方法详解
- 01-10深入二叉树两个结点的最低共同父结点的详解
- 01-10数据结构课程设计- 解析最少换车次数的问题详解
- 01-10数据结构课程设计-用栈实现表达式求值的方法详解
- 01-10HDOJ 1443 约瑟夫环的最新应用分析详解
- 01-10使用C++实现全排列算法的方法详解
- 01-10如何查看进程实际的内存占用情况详解
- 01-10深入Main函数中的参数argc,argv的使用详解
- 01-10APUE笔记之:进程环境详解
- 01-10深入第K大数问题以及算法概要的详解
阅读排行
本栏相关
- 04-02c语言函数调用后清空内存 c语言调用
- 04-02func函数+在C语言 func函数在c语言中
- 04-02c语言的正则匹配函数 c语言正则表达
- 04-02c语言用函数写分段 用c语言表示分段
- 04-02c语言中对数函数的表达式 c语言中对
- 04-02c语言编写函数冒泡排序 c语言冒泡排
- 04-02c语言没有round函数 round c语言
- 04-02c语言分段函数怎么求 用c语言求分段
- 04-02C语言中怎么打出三角函数 c语言中怎
- 04-02c语言调用函数求fibo C语言调用函数求
随机阅读
- 01-10使用C语言求解扑克牌的顺子及n个骰子
- 01-11ajax实现页面的局部加载
- 08-05织梦dedecms什么时候用栏目交叉功能?
- 08-05dedecms(织梦)副栏目数量限制代码修改
- 01-10SublimeText编译C开发环境设置
- 01-11Mac OSX 打开原生自带读写NTFS功能(图文
- 04-02jquery与jsp,用jquery
- 01-10C#中split用法实例总结
- 08-05DEDE织梦data目录下的sessions文件夹有什
- 01-10delphi制作wav文件的方法