右值&&左值
c++11增加了一个新的类型,右值引用,记作:&&
左值是指在内存中有明确的地址,我们可以找到这块地址的数据(可取地址)
右值是只提供数据,无法找到地址(不可取地址)
所有有名字的变量都是左值,而右值 是匿名的。
一般情况下位于等号左边的是左值,位于等号右边的是右值,但是也可以出现左值给左值赋值的情况
c++11中右值分为两种情况:一个是将亡值,一个是纯右值.
将亡值:与右值引用相关的表达式,比如:T&&类型函数的返回值,std::move()的返回值等
纯右值:非引用返回的临时变量,运算表达式产生的临时变量,原始字变量,lambda表达式等
左值引用:
左值引用就是我们平常使用的“引用”。引用是为对象起的别名,必须被初始化,与变量绑定到一起,且将一直绑定在一起。
我们通过 & 来获得左值引用,可以把引用绑定到一个左值上,而不能绑定到要求转换的表达式、字面常量或是返回右值的表达式。这个没啥说的,像拷贝构造啥的平时都能用到。
右值引用:
右值引用就是对右值引用的类型。因为右值是匿名的,所以我们只能通过引用的方式找到它。无论是左值引用还是右值引用都必须被初始化,因为引用类型本身并不拥有所绑定对象的内存,只是该对象的一个别名。通过右值引用,该右值所占的内存又可以被使用。
int&& vaule = 520; //右值引用,520是字面量,是右值
class Test
{
public:
Test()
{
cout<<"构造函数"<<endl;
}
Test(const Test&a )
{
cout<<"拷贝构造函数"<<endl;
}
};
Test getObj()
{
return Test();
}
int main()
{
int a1;
//int && a2 = a1; 报错 右值引用不能被左值初始化
//Test & t1 = getObj(); 右值不能初始化左值引用
Test && t2 = getObj(); //函数返回的临时对象是右值,可以被调用
const Test & t3 = getObj(); //常量左值引用是万能可以接左值
coonst int & t3 = a1; //被左值初始化
return 0;
}
右值引用的用处
在c++用对象初始化对象时会调用拷贝构造,如果这个对象占用堆内存很大,那么这个拷贝的代价就算是非常大的,在某些情况,如果想避免对象的深拷贝,就可以使用右值引用进行性能的优化。
右值引用具有移动语义,移动语义可以将堆区资源,通过浅拷贝从一个对象转移到另一个对象这样就能减少不必要的临时对象创建,拷贝以及销毁,大幅度提高性能
结论:需要动态申请大量的资源的类,应该设计移动构造,提高程序的效率,需要注意的是在提供移动构造的同时,一般也会提供左值引用拷贝构造函数,左值初始化新对象时会走拷贝构造
右值引用的特点:
c++中,并不是所有情况下&&都代表右值引用,在模板和自动类型推导(auto)中,如果是模板参数需要指定为T&&,如果是自动类型推导需要指定为auto&&,这两种情况下&&被称作未定的引用类型。另外constT&&表示一个右值引用,不是未定引用类型。因为T&&或者auto&&这种未定引用类型作为参数时,有可能被推导成右值引用,也有能被推导为左值引用,在进行类型推导时右值引用会发生变化,这种变化被称为引用折叠,折叠规则如下:
通过右值 推导T&&或者auto&&得到的是一个右值引用类型,constT&&表示一个右值引用
通过非右值(右值引用,左值,左值引用,常量右值引用,常量左值引用)推导T&&或者auto&&得到的是一个左值引用类型.
int main()
{
int && a1 = 1; //右值推导为 右值引用
auto && bb = a1; //右值引用推导为 左值引用
auto && bb1 = 2; //右值推导为 右值引用
int a2 = 1;
int &a3 = a2; //左值推导为 左值引用
auto && cc = a3; //左值引用推导为 左值引用
auto && cc1 = a2; //左值推导为 左值引用
const int & s1 = 1; //常量左值引用
cosnt int && s2 = 1; //常量右值引用
auto && dd = s1; //常量左值引用推导为 左值引用
auto && ee = s2; //常量右值引用推导为 左值引用
return 0;
}
move
c++11添加了右值引用,却不能左值初始化右值引用,在一特定的情况下免不了需要左值初始化右值引用(用左值调用移动构造),如果想要用左值初始化一个右值引用需要借助std::move()函数。move()函数可以将左值比换为右值
forward
右值引用类型是独立于值的,一个右值引用作为函数参数的形参时,在函数内部转发该产生给内部其他函数时,他就变成了一个左值(当右值被命名时编译器会认为他是个左值),并不是原来的类型了。如果按照参数原来的类型转发到另一个函数,可以使用c++11的 std::forward()函数,该函数实现的功能称之为完美转发
//函数原型
template<class T>T&&forward(typename remove_reference<T>::type & t)noexcept;
template<class T>T&&forward(typename remove_reference<T>::type && t)noexcept;
//精简之后的样子
std::forward<T>(t);
//当T为左值引用类型时,t将会被转换为左值
//当T不是左值引用类型时,t将会被转换为T类型的右值
#include<iostream>
using namespace std;
template<typename T>
void printValue(T& t)
{
cout<<"左值引用:"<<t<<endl;
}
template<typename T>
void printValue(T&& t)
{
couot<<"右值引用:"<<t<<endl;
}
template<typename T>
boid testForward(T && v)
{
printValue(v);
printValue(move(v));
printValue(forward<T>(v));
cout<<endl;
}
int main()
{
testForward(520);
int num = 1314;
testForward(num);
testForward(forward<int>(num));
testForward(forward<int&>(num));
testForward(forward<int&&>(num));
return 0;
}