什么是左值,什么是右值?
不可以单纯字面去理解,等号左边是左值,等号右边是右值。
左值:可以修改的可以认为是左值,左值通常是变量。
右值:通常是常量,表达式或函数返回值(临时对象)。
int main()
{//左值引用的定义int a = 0;int& b = a;//右值引用的定义int x = 1,y = 2;int&& c = 10;int&& d = x+y;//左值引用不能引用右值,const左值引用可以const int &e = 1;//右值引用不能引用左值,需要将左值move成右值才可以int&& c = move(a);
}
c++11将右值区分为:纯右值和将亡值
纯右值:基本类型的常量或者临时对象
将亡值:自定义类型的临时对象
纯右值我们上面已经介绍了,下面我们来介绍一下将亡值:
class String
{
public:String(const char* str = "")//默认构造{_str = new char[strlen(str)+1];strcpy(_str,str);}String(const String& s)//拷贝构造{cont<<"String(const String& s)深拷贝"<<endl;_str = new char[strlen(s._str)+1];strcpy(_str,s._str);}String& operator=(const String& s)//=重载{cout << "String& operator=(const String& s)深拷贝" << endl;if (this != &s){char* newstr = new char[strlen(s._str) + 1];strcpy(newstr, s._str);delete[] _str;_str = newstr;}return *this;}~String(){delete[] _str;}
private:char* _str;
};
**
右值的意义:
**
这是我们认识右值引用前简单模拟的string类。左值引用帮我们减少了拷贝。但是他有一些缺陷:
如果传过来的引用是一个将亡值,也就是马上就要亡的值(等出了这个作用域,他可能也不用了,就析构了),那我们还拷贝他的内容,多浪费啊,直接一个swap,交换一下地址,把他的值拿过来用,这样就不用拷贝了,提高了效率。而且也是将亡值了会自己析构,还省去了我们析构的事情。
但是!我们如何区分传过来的值是不是将亡值?
那么右值引用就帮我们解决了这个问题。
拷贝构造:
String(const String& s)
{cont<<"String(const String& s)深拷贝"<<endl;_str = new char[strlen(s._str)+1];strcpy(_str,s._str);
}
String(String&& s):_str(nullptr)
{cont<<"String(String&& s)移动拷贝"<<endl;swap(_str,s._str);
}
operator=
String& operator=(const String& s)
{cout << "String& operator=(const String& s)深拷贝" << endl;if (this != &s){char* newstr = new char[strlen(s._str) + 1];strcpy(newstr, s._str);delete[] _str;_str = newstr;}return *this;
}
String& operator=(const String&& s)
{cout << "String& operator=(const String&& s)移动赋值-效率高" << endl;swap(_str, s._str);return *this;
}
右值引用本身没有太多意义,右值引用实现了移动构造和移动赋值。
那么面对接受函数传值返回对象(右值)等场景,可以提高效率
总结:
右值引用做参数和做返回值减少拷贝的本质是利用了移动构造和移动赋值
左值引用和右值引用本质的作用都是减少拷贝,右值引用本质可以认为是在弥补左值引用不足的地方,他们俩相辅相成
c++98
左值引用:解决的是传参过程中的返回值过程中的拷贝
做参数:void f1(T x)->void f1(const T& x) 传参过程中减少拷贝
做返回值:T f2()->T& f2() 解决返回值过程中的拷贝
但是要注意这里有限制,如果返回对象出了作用域不在了就不能传引用,这个左值引用无法解决,等待c++11右值引用解决
c++11
右值引用:解决的是传参后,push/insert函数内部将对象移动到容器空间上的问题+传值返回接受 返回值的拷贝
做参数:void push(T&& x) 解决push内部不在使用拷贝构造x到容器空间上而是移动构造过去
做返回值:T f2() 解决的外面调用接受f2()返回对象的拷贝,T ret = f2()
这里就是右值引动的移动构造,减少了拷贝
解决右值属性丢失问题:完美转发
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
// 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
// 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,
// 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,
// 我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发
template<typename T>
void PerfectForward(T&& t)
{Fun(t);//右值引用会在第二次之后的参数传递过程中右值属性丢失,下一层调用会全部识别为左值//完美转发解决
}
int main()
{PerfectForward(10);// 右值int a;PerfectForward(a); // 左值PerfectForward(std::move(a)); // 右值const int b = 8;PerfectForward(b);// const 左值PerfectForward(std::move(b)); // const 右值return 0;
}
运行后发现所有右值经过第二次传递后右值属性丢失,下一层全部识别为左值
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
// std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
template<typename T>
void PerfectForward(T&& t)
{Fun(std::forward<T>(t));
}
int main()
{PerfectForward(10);// 右值int a;PerfectForward(a);// 左值PerfectForward(std::move(a)); // 右值const int b = 8;PerfectForward(b); // const 左值PerfectForward(std::move(b)); // const 右值return 0;}