1、左值右值区分:
左值有名字,是变量,有储存区域,可取地址;右值为临时量,比如函数返回值,lambula表达式
2、右值引用引出原因:
实现自定义的浅拷贝(移动赋值函数),节省new堆内存的消耗
我们知道如果没实现"="赋值操作符函数的重载的话,类会有默认的赋值函数,但是实现的是默认浅拷贝(默认复制赋值函数)
浅拷贝:如果是常类型,直接赋值;如果是类类型,会将类中存在的指针进行指针赋值指向,但并没有真正给指针开辟新的内存
深拷贝:常类型同上;类类型,为指针开辟新内存
为什么要实现自定义的浅拷贝:因为系统默认的浅拷贝,会造成二次析构造成崩溃
自定义的浅拷贝可以实现将新指针指向旧指针的内存地址,并将旧指针置空,这样析构的时候旧指针为空,判空返回不析构这块内存
那么为什么要实现自定义的浅拷贝:因为在我们赋值拷贝的时候,不需要每个变量都给他开辟内存,赋值相同的内容,将指针指向旧指针的内存地址,旧指针置空,那新指针是不是就获得这块内存地址的使用权了呢 举个例子
class A
{
A(){}
~A(){ if(b)delete b;}
A&operater=(A&otherA) //默认浅拷贝(默认复制赋值函数,有些地方也叫默认拷贝赋值函数)
{
this->b = otherA.b;
return *this;
}
A&operater=(A&otherA) //自定义深拷贝(复制赋值函数)(自定义"="赋值操作符后,优先重载使用自定义的深拷贝,上面的浅拷贝不使用)
{
this->b = new B(*otherA.b);
return *this;
}
A&operater=(A&&otherA) //自定义浅拷贝(移动赋值函数)(那么还想用跟深拷贝不一样的浅拷贝怎办,利用右值引用(参数不同重载))
{
this->b = otherA.b;
otherA.b = nullpter;//这样就不会有两个指针指向同一份地址,析构两次地址
return *this;
}
B* b;
}
// 交换的例子
int main
{
A a1;//假设类中的b指针已经有堆内存地址
A a2;
A temp = a1; //这里的赋值都是走的第二个函数深拷贝,会发现好几次b指针的new的操作,挺浪费内存的
a1 = a2;
a2 = temp;
}
//我们优化下看看 ,其实只用交换a1和a2的b一直就可以了;我们来使用std::move可以将左值强制转换成右值
int main
{
A a1;//假设类中的b指针已经有堆内存地址
A a2;
A temp = std::move(a1); //这里的赋值都是走的第三个函数自定义的浅拷贝(重载右值引用参数),不会产生new申请内存,而是交换指针指向
a1 = std::move(a2);
a2 = std::move(temp );
}
// 由上看出,右值引用是为了节省重复申请内存空间,它是节约空间的好帮手
3、左值右值在函数中传递中的一致性(forward):
test(A&&a) //作为参数a为右值引用
{
A a2 = a;// 这里a为左值引用,因为有名字,是变量,所以还是会走到第二个深拷贝的地方,这里出现了右值->左值的变化
}
那我们怎么办:使用foward函数
优化后的写法
test(A&&a) //作为参数a为右值引用
{
A a2 = std::forward(<A>(a));//那么这里std::forward的结果还是右值引用,所以可以正确调用到我们自定义的浅拷贝第三个函数
}
4、叠加原则
叠加原则:
A&+A&& = A&
A&&+A& = A&
A&+A& = A&
A&&+A&& = A&&
简单来说,只有右值引用叠加右值引用才是右值引用,其他的叠加都是左值
右值引用的模板推到类型也是遵循次规则,不重复写
5、forward和move
forward和move其实都是利用引用叠加原则去实现自己的功能
forward:引用参数传递的过程中保持一致,不改变,函数是右值,用这个值的时候还是右值,保持不改变的特性
move:将引用参数转化为右值引用
forward原型
根据参数左右值选择不同的调用格式
template<class _Ty> inline
constexpr _Ty&& forward(
typename remove_reference<_Ty>::type& _Arg) _NOEXCEPT
{ // forward an lvalue as either an lvalue or an rvalue
return (static_cast<_Ty&&>(_Arg));
}
调用格式:forward(<A>a) //返回右值引用:_Ty变成A,_Ty&&变成A&&,最后类型转换成A&&右值引用
调用格式:forward(<A&>a) //返回左值引用:_Ty变成A&,_Ty&&变成A& &&,根据叠加原则,最后类型转换成A&&左值引用
这里的a是变量,所以是左值,所以调用上面左值引用参数的forward
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
template<class _Ty> inline
constexpr _Ty&& forward(
typename remove_reference<_Ty>::type&& _Arg) _NOEXCEPT
{ // forward an rvalue as an rvalue
static_assert(!is_lvalue_reference<_Ty>::value, "bad forward call");
return (static_cast<_Ty&&>(_Arg));
}
调用格式:forward(<A>A()) //返回右值引用 :_Ty变成A,_Ty&&变成A&&,最后类型转换成A&&右值引用
调用格式:forward(<A&>A()) //返回左值引用:_Ty变成A&,_Ty&&变成A& &&,根据叠加原则,最后类型转换成A&&左值引用
这里的A()是临时变量,所以是右值,所以调用上面右值引用参数的forward
特别说一句,为什么用引用移除remove_reference,笔者也不太清楚,笔者亲身试验过,将他们改成这样没有remove_reference的样纸,结果也是一样的效果,所以笔者觉得可能是为了美观...
constexpr _Ty&& forward1(_Ty& _Arg) {}
constexpr _Ty&& forward1(_Ty&& _Arg) {}
可能读者会有一个疑问:我们forward(<A&>a)调用,typename remove_reference<_Ty>::type& _Arg,_Ty变成A&,式子变成
typename remove_reference<A&>::type& _Arg,有两个&诶,那不是变成A&&,变成右值引用了么,其实笔者也有这样的疑问,不过最后笔者克制住了,强势把它理解成A&类型的一个左值引用,可能有些牵强,不过笔者也找不到合适的解释了
大家如果对上述两点有好的理解也可以告诉笔者我...以后我知道了也会更新...
move原型
//返回右值引用
template<class _Ty> inline
constexpr typename remove_reference<_Ty>::type&&
move(_Ty&& _Arg) _NOEXCEPT
{ // forward _Arg as movable
return (static_cast<typename remove_reference<_Ty>::type&&>(_Arg));
}
同样不知道为何写remove_reference...
总结:
右值引用是为了节约拷贝时候的内存地址开销,实现自定义的移动赋值函数,将指针交换进行浅拷贝
左值是变量有名字有地址,右值是临时变量,特别说明,右值引用是左值
forward的功能是保持引用类型前后一致,forward<A&>a是左值引用,forward<A>a是右值引用
move的功能是返回一个右值引用
还有些不明白的地方,比如remove_reference为何存在于forward和move中,比如typename remove_reference<A&>::type&到底是理解成A&的一个左值引用,还是有什么其他好的理解方式呢,反正理解成A&&右值引用肯定错的.....
大家如果对上述两点有好的理解也可以告诉笔者我...以后我知道了也会更新...
【参考文献】
http://www.cnblogs.com/catch/p/5019402.html
http://www.cnblogs.com/catch/p/3507883.html
http://thbecker.net/articles/rvalue_references/section_01.html
https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers