什么是左值?什么是右值?
这里的左右不代表位置,也就是说左值不是左边的值,右值也不是右边的值;
这里的左右仅仅是一种叫法。
那什么是左值呢? 左值通常来说就是一个变量;例如: int x=10; 则x就是一个左值!
那么什么是右值呢?右值通常来说是一种不可改变的值,例如:刚才的常量10,临时对象(表达式计算后的结果;函数返回的值);而右值又可以分为纯右值和将亡值。纯右值是指基本类型的常量或者临时对象,将亡值是指自定义类型的临时对象。
int x=10; int y=2; int z=x+y; 这里的10和2就是纯右值,是基本类型的常量,x+y计算的结果会有一个临时对象存储,这个临时对象也是纯右值。
右值引用和左值引用的本质,都是起别名;区别:左值引用是给左值起别名,右值引用是给右值起别名
左值引用
int x=10;
int& y=x;//左值引用
int& y=10;//会编译出错,但是不能对右值进行左值引用吗?
可以对右值进行左值引用 但要加上const 关键字 eg: const int& y=10 //不会编译报错
右值引用
int&& x=10;
int y=1;
int z=2;
int&& aa=y+z;
应用:
右值引用最主要莫过于 右值引用的移动语义:右值引用的移动拷贝和移动赋值可以减少拷贝次数,从而提高效率。
我们可以从下面string类的现代写法中去体会
class mystring{
public:
mystring(char* str = "")
{
if(str==nullptr)
str="";
int size = strlen(str);
int capacity = size + 1;
_str = new char[capacity];
strcpy(_str, str);
}
~mystring()
{
if (this != nullptr)
{
delete[] _str;
}
_str = nullptr;
}
//s1(s2)
mystring(const mystring& str)
:_str(nullptr)
{
mystring tmp(str._str);
std::swap(_str, tmp._str);
}
mystring(mystring&& str)
:_str(str._str)
{
str._str = nullptr;
}
//赋值运算符重载
//s3=s4
mystring& operator=(const mystring& str)
{
if (this != &str)
{
mystring tmp(str._str);
std::swap(_str, tmp._str);
}
return *this;
}
mystring& operator=(mystring&& str)
{
_str = str._str;
str._str = nullptr;
return *this;
}
private:
char* _str;
};
拷贝赋值和赋值重载函数原本需要进行深拷贝,如果传入的右值(将亡值)的话,我们直接可以掠夺将亡值的资源,进行移动赋值和移动拷贝,只需要进行浅拷贝。效率自然而言的也就得到了提升。
赋值返回
我们只可以从右值引用的移动语义解决了那些问题的角度去理解右值引用的作用。
mystring operator+(const mystring& str)
{
char* tmp = new char[strlen(str._str) + strlen(_str) + 1];
strcpy(tmp, _str);
strcpy(tmp + strlen(_str), str._str);
mystring ret(tmp);
return ret;
}
mystring str("Hello ");
mystring str1("World");
mystring str2 = str + str1;
如果没有右值引用,传值返回的时候,我们需要用ret构造一个临时对象,临时对象构造完毕后ret会被销毁,在用临时对象构造str2;也就是需要一次拷贝构造,而程序的运行结果(如下图)也佐证了我们的想法。而有了右值引用之后,我们就可以用效率高的移动构造代替拷贝构造。
通过上面,我们可以理解右值引用的移动语义,就是实现资源的转移,将深拷贝转换为代价小的浅拷贝
右值引用引用左值
之前我们说过,右值引用只能引用右值,但是使用左值被move之后可以进行右值引用
eg:
int x=10;
int&& y=move(x);
但是在使用move的时候一定要小心
mystring str("Hello "); mystring str1("World"); mystring str2(std::move(str));
在运行完 mystring str2(std::move(str));语句后,str是一个null
完美转发
完美转发是指在函数模板中,完全按照模板的参数的类型,将参数按照模板类型,传递给另一个函数(所谓的完美就是,左值转发后还是左值,右值转发后还是右值)
上面的左图实现了完美转发,而右图中传递的参数属性发生变化,即右值变成了左值
即为了实现转发的时候,参数属性不变,C++11中我们需要使用forward函数(完美转发)
总结:
引用就是起别名,无论是左值引用还是右值引用本质都是起别名;
左值引用做参数 void Fun(A& a)还是做返回值 mystring& operator=(const mystring& str) 都是通过减少拷贝的次数,从而提高效率;但是左值引用在做返回值的时候,存在一个巨大的限制(不能出对应的作用域,一旦出了作用域,就会出现程序错误,而右值引用就可以用来解决这个问题)
右值引用在做参数的,做返回值的时候都可以通过移动语句减少拷贝的次数,并且不会出现左值引用做返回值的问题。
右值引用最主要的用途就是移动语义(移动拷贝和移动赋值)