个人理解,仅供参考!
左值与右值
一个简单的区分方法是,左值是可寻址的,而右值是不可被寻址的。更粗糙地看,一个赋值表达式的左边就是左值,右边就是右值。
左值引用
1 | int a = 1; |
这里我们称ref
为a
的引用,ref
就是一个左值引用。左值引用只可引用左值,尝试引用右值编译器会报错:
而我们可以通过const
来将一个左值引用绑定到一个右值上:
1 | const int& ref = 1; |
此时我们称ref
是一个常量引用,其值不可被更改。
左值引用的局限性
1 | std::vector<my_class> vec; |
在这个例子中,我们要在容器vec
中添加一个元素,于是创建了一个临时变量temp
,再将其添加到容器中。
而实际上,以std::vector::push_back()
为例,这个过程中会创建一个temp
的副本并添加到数组的末尾,也就是说,会调用my_class
的拷贝构造函数。
那这就产生了一个问题,如果my_class
的拷贝是深拷贝,那么先创建一个后续大概率不会再使用的临时对象temp
,再将其拷贝到容器中,效率太低了。有没有方法直接在容器中创建这个对象?或者有没有办法使得在容器中的新对象,可以直接“窃取”temp
的所有资源,而不是再拷贝一份(这也就是所谓的移动语义)?
我们来研究这个问题,以下是std::vector::push_back()
的签名:
1 | void push_back(const value_type& __x); |
可以看到,这个方法的参数类型是常量左值引用,也就意味着传入的参数对象是不可变的。假如我们在这个方法中实现移动语义,新对象可以正确地持有参数对象的资源,但参数对象也仍然持有这些资源。所以我们需要一个新的push_back
的重载,它接收一个可变的参数类型,并在新对象获得参数对象的资源后,将参数对象指向资源的指针置空。
右值引用
上面提到的可变的参数类型实际就是右值引用,<type>&&
表示<type>
类型的右值引用:
1 | my_class&& ref = my_class(); |
有了右值引用之后,我们就可以实现移动语义了:
1 | class my_class { |
从上面的两版拷贝构造函数中我们可以看到,常量左值引用版本的是拷贝参数对象的资源的值,而右值引用版本则是直接将自身的指针指向参数对象对应的资源,并将参数对象的指针置空。这样,我们就获得了两个实现不同功能的拷贝构造函数,其中,右值引用的版本实现了移动语义。
使用右值引用
有了上面的右值引用版本的拷贝构造函数之后,我们就可以将一个对象的资源“移动”给另一个对象了,这时候我们需要用std::move()
来获取原对象的右值引用:
1 | //将temp的资源“移动”给obj |
对于std::move()
的实现,本文不做深入探讨,我们仅需知道它可以返回一个对象的右值引用即可。