左值,右值,左值引用,右值引用

# 从四行代码看右值引用
1. 右值引用是C++11新增加的一个重要特性,它主要解决两个问题:1)临时对象非必要的昂贵的拷贝操作;2)在模板函数中如何按照参数的实际类型进行转发
2. 第一行代码:int i = getVar();

这行代码使用getVar()函数产生一个整型值,实际上这行代码生成了两种类型的值,一种是左值i,在表达式结束时依然存在,另一种是getVar()返回的临时值,这种临时值在表达式结束的时候就被销毁了,这个临时值就是右值。左值是具名的,右值是不具名的。一般而言,如果能对表达式取地址,则此表达式为左值,否则为右值。所有的具名变量都是左值,所有的匿名变量都是右值。lamda表达式就是右值。右值的特点就是表达式结束后就被销毁了。
3. 第二行代码:T&& k = getVar();

这行代码多了一个“&&”,这就是右值引用。由于右值是匿名变量,所以我们只能通过右值引用来获取右值。根据第二条描述的,一般右值在表达式结束时就被销毁了,为了给右值续命,我们可以使用右值引用,只要右值引用类型的变量没有被销毁,右值临时值就一直存在。利用右值引用可以延长右值生命周期,避免临时变量的拷贝构造和析构,提高性能。在之前的C++版本中,常量左值引用也可以达到同样的效果。常量左值引用是一个万能的引用类型,它可以接收左值,右值,常量左值,常量右值。而普通的左值引用是不能接收右值的。比如T& k = getVar()就是错误的。

右值引用的另一个特点是右值引用独立于左值和右值,也就是说,右值引用类型的变量既可以是左值,又可以是右值。比如T&& t = 0, t就是右值,int x = 0; T&& t = x, t就是左值。正是由于右值引用既可以是左值,又可以是右值,这都依赖于初始化,我们可以利用这一点做文章,比如移动语义(move)和完美转发(forward).
4. 第三行代码:T(T&& a): m_val(a.m_val){a.m_val = nullptr;}

这其实是一个移动构造函数。这里是将右值引用作为构造函数的参数。一般一个带有堆内存的类必须提供一个深拷贝拷贝构造函数,因为默认的拷贝构造函数是浅拷贝,会发生”指针悬垂“问题,也就是堆内存可能会被删除两次。提供深拷贝的拷贝构造函数有时候会造成性能的损失,尤其是临时变量在拷贝完成后就被销毁了,当堆内存很大时,调用拷贝构造函数再调用析构函数会造成性能的浪费。我们可以定义移动构造函数来解决这个问题。实际上第三行代码就是一个移动构造函数。这个函数没有做深拷贝,而是仅仅将指针的所有者转移到了另一个对象。同时将原对象的指针置空。这就解决了“临时变量的深拷贝问题”。移动构造函数的参数是一个右值引用类型。#临时变量的拷贝会自动匹配到移动构造函数,因为这个函数的参数是一个右值引用类型。# 也就是说,对于临时值,我们只需要做浅拷贝就行,无需做深拷贝,这样就解决了临时变量深拷贝造成性能损失的问题。这就是所谓的移动语义。*右值引用的一个重要作用就是支持移动语义。
*移动语义是通过右值引用来匹配临时值的。
*普通的左值引用也可以通过移动语义来优化性能。这个时候就需要用到move函数。move可以将左值转换为右值,从而应用移动语义
*move是将一个对象的资源转移到另一个对象,只有转移而没有内存的拷贝。这就是move语义。(需要注意的是,move唯一的功能就是将一个左值转换为一个右值引用,对于内置类型如int,char,应用move依然会发生拷贝,因为它们没有对应的移动构造函数。move对含有资源(堆内存或者句柄)的对象更有意义。)*
5. 第四行代码:template<typename T> void f(T&& val){foo(forward<T>(val));}

这里是实现完美转发在保持参数左值或者右值特征的前提下,将参数传递给模板函数内调用的另一个函数。T&& val属于未定引用,forward会按参数的实际类型匹配对应的重载函数,实现完美转发。(完美转发的一个好处是实现移动语义,比如实际参数类型是左值可以匹配到拷贝构造函数,实际类型是右值则匹配到移动构造函数。)

猜你喜欢

转载自www.cnblogs.com/conanpeng/p/12806283.html
今日推荐