rambo

左值引用与右值引用

左值和右值

左右和右值都是针对表达是而言的。
左值是表达式结束后依旧存在的对象,它不光有值,还有一个存储地址。
右值是表达式结束就不再存在的临时对象,可以认为它只有一个值的大小,没有存储地址,只关心它的值。(An rvalue is a temporary value that does not persist beyond the expression that uses it。)

左值引用和右值引用

左值引用:&

左值引用视为对象的另一名称。 左值引用声明由说明符的可选列表后跟一个引用声明符组成。 引用必须初始化且无法更改
地址可转换为给定指针类型的任何对象也可转换为相似的引用类型。 例如,地址可转换为类型 char * 的任何对象也可转换为类型 char &。

在标准C++语言中,临时量(术语为右值,因其出现在赋值表达式的右边)可以被传给函数,但只能被接受为const &类型。这样函数便无法区分传给const &的是真实的右值还是常规变量。而且,由于类型为const &,函数也无法改变所传对象的值。

右值引用:&&

引入背景:临时对象的产生和拷贝所带来的效率折损,一直是C++所为人诟病的问题。C++11将增加一种名为右值引用的新的引用类型,记作typename &&。这种类型可以被接受为非const值,从而允许改变其值。这种改变将允许某些对象创建转移语义,减少不必要的资源拷贝。

vector 中添加一个元素,这个动作需要先后调用 string::string(const char*), string::string(const string&), string::~string() 三个函数,涉及两次内存拷贝:第一次使用字面常量 “string” 构造出一个临时对象,第二次使用该临时对象构造出 vector 中的一个新元素,『最后临时对象会发生析构』。

又比如:

在 Visual C++ 2010 以前, 每次 operator+ 调用,分配且返回了一个新的临时 string 对象 ( 右值 )。operator+ 不能将一个字符串追加到另一个字符串,因为它不知道源字符串是左值还是右值。 如果两个源字符串都是左值,则它们可能会在程序中的其他位置引用,因此不能修改。而右值不能在程序中的其他位置引用,利用右值引用,可以将 operator+ 修改为采用右值。

移动语义的概念:临时对象的构造和析构带来了不必要的资源拷贝。如果有一种机制,可以在语法层面识别出临时对象,在使用临时对象构造新对象(拷贝构造)的时候,将临时对象所持有的资源『转移』到新的对象中,就能消除这种不必要的拷贝。

对于移动语义的拷贝『构造』,一般流程是将源对象的资源绑定到目的对象,然后解除源对象对资源的绑定;对于赋值操作,一般流程是,首先销毁目的对象所持有的资源,然后改变资源的绑定。在进行大数据复制的时候,将动态申请的内存空间的所有权直接转让出去,不用进行大量的数据移动,尤其是在vector作为返回值的时候。

被移动语义的数据交出了所有权,为了不出现析构两次同一数据区,要将交出所有权的数据的指向动态申请内存去的指针赋值位nullptr,即空指针,对空指针执行delete[]是合法的。

 

比如,一个std::vector,就其内部实现而言,是一个C式数组的封装。如果需要创建vector临时量或者从函数中返回vector,那就只能通过创建一个新的vector并拷贝所有存于右值中的数据来存储数据。之后这个临时的vector则会被销毁,同时删除其包含的数据。有了右值引用,一个参数为指向某个vector的右值引用的std::vector的转移构造器就能够简单地将该右值中C式数组的指针复制到新的vector,然后将该右值清空。这里没有数组拷贝,并且销毁被清空的右值也不会销毁保存数据的内存。返回vector的函数现在只需要返回一个std::vector<>&&。如果vector没有转移构造器,那么结果会像以前一样:用std::vector<> &参数调用它的拷贝构造器。如果vector确实具有转移构造器,那么转移构造器就会被调用,从而避免大量的内存分配。

 

 

与默认复制构造函数不同,编译器不提供默认移动构造函数。