C++11右值引用和移动语义

右值引用和移动语义

右值和左值

左值的本质是 “可以定位(locatable)的实体”

  • 可被取地址:能使用&运算符获取其内存地址(&lvalue合法);
  • 可作为赋值运算符的左操作数:如x = 5中,x是左值;
  • 生命周期较长:通常是有名字的变量、对象,或在作用域内长期存在的实体;
  • 可被左值引用(T&)绑定:如int& ref = x(x是左值)。

右值本质是临时的、不可持久化的、即将被销毁的值

  • 无法被取地址(&运算符对右值无效);
  • 通常是临时的、即将销毁的值(如字面量、临时对象、表达式结果);
  • 不能被赋值(如3 = x;是错误的)。在这里插入图片描述

右值引用与左值引用

  • Type& r1 = x; Type&& rr1 = y; 第⼀个语句就是左值引⽤,左值引⽤就是给左值取别名,第⼆个就是右值引⽤,同样的道理,右值引⽤就是给右值取别名。
  • 左值引⽤不能直接引⽤右值,但是const左值引⽤可以引⽤右值
  • 右值引⽤不能直接引⽤左值,但是右值引⽤可以引⽤move(左值)
  • template typename remove_reference::type&& move (T&&arg);move是库⾥⾯的⼀个函数模板,本质内部是进⾏强制类型转换,当然他还涉及⼀些引⽤折叠的知识,这个我们后⾯会细讲。
  • 需要注意的是变量表达式都是左值属性,也就意味着⼀个右值被右值引⽤绑定后,右值引⽤变量变量表达式的属性是左值

const左值引用可以引用右值,右值引用可以引用move左值。

引用延长生命周期

两种引用都能延长右值的生命周期
右值引用本身是左值。在这里插入图片描述

左值和右值的参数匹配

const左值引用可以接收右值,有右值引用参数后,右值实参优先匹配右值引用版本。
这里可以理解之前的参数尽量使用const的原因,是为了能接收临时对象(右值)在这里插入图片描述
在这里插入图片描述

移动语义

移动构造和移动赋值都是窃取引用的右值的资源,避免像拷贝构造和赋值中的深拷贝,从而提高效率。

移动语义窃取右值资源的原理

变量表达式都是左值属性,所以一个右值被右值引用绑定后属性变为左值。由此右值可以修改,就可以实现移动语义对右值资源的窃取。

C++11之前const左值引用就可以引用右值,为什么不能实现移动语义

const左值引用不能修改,而右值引用,引用右值后属性变为左值,可以修改,由此可以窃取右值的资源
在移动语义中是将引用对象的资源交换过来(修改引用对象)
在这里插入图片描述

所以移动语义对于有资源的类才有意义。
之前拷贝构造参数const T&,传值返回时调用拷贝构造时,const T&可以接收左值和右值
move(左值)将左值转换为右值引用,标记为将亡值(生命周期即将结束)。在这里插入图片描述

右值引用移动语义在传值返回的效率提升

有移动构造参数T&&右值引用,如果是传值返回(被识别为右值),可以调用移动构造,避免拷贝,提高效率在这里插入图片描述
编译器对传值返回的优化
在这里插入图片描述

右值引用移动语义在传参的效率提升

本质是转移实参(右值)的资源避免拷贝
容器插入:
在这里插入图片描述
每次传递都需要move,因为右值被右值引用接收后变为左值,再传递会被左值引用接收。

引用折叠和万能引用

引用折叠

通过模板可以实现引用折叠
引用折叠规则:只要有左值引用,就是左值引用
在这里插入图片描述
在这里插入图片描述

万能引用

依赖于引用折叠,可以同时接收左值和右值转化为对应引用,根据接收的实参推导引用在这里插入图片描述

完美转发

能够在函数调用过程中完整保留实参的类型和值类别(左值 / 右值属性),将参数原封不动地转发给其他函数。
核心目标
完美转发要实现两个核心目标:

  1. 保留值类别:左值实参转发后仍为左值,右值实参转发后仍为右值。
  2. 保留 const/volatile 等属性:常量对象转发后仍保持常量性。

实现原理
完美转发依赖两个关键机制:
4. 万能引用(Universal Reference):在模板推导语境下的 T&&,能接受左值和右值并推导其类型。
5. std::forward 函数:根据模板参数类型,将万能引用恢复为原始值类别。在这里插入图片描述
使用完美转发可以实现左值和右值版本接口的合并

可变参数模板

概念
C++11⽀持可变参数模板,也就是说⽀持可变数量参数的函数模板和类模板,可变数⽬的参数被称为参数包,存在两种参数包:模板参数包,表⽰零或多个模板参数;函数参数包:表⽰零或多个函数参数。
• template void Func(Args… args) {}
• template void Func(Args&… args) {}
• template void Func(Args&&… args) {}
• 我们⽤省略号来指出⼀个模板参数或函数参数的表⽰⼀个包,在模板参数列表中,class…或 typename…指出接下来的参数表⽰零或多个类型列表;在函数参数列表中,类型名后⾯跟…指出 接下来表⽰零或多个形参对象列表;函数参数包可以⽤左值引⽤或右值引⽤表⽰,跟前⾯普通模板⼀样,每个参数实例化时遵循引⽤折叠规则。
• 可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。
• 这⾥我们可以使⽤sizeof…运算符去计算参数包中参数的个数。

包扩展

对于⼀个参数包,我们除了能计算他的参数个数,我们能做的唯⼀的事情就是扩展它,当扩展⼀个 包时,我们还要提供⽤于每个扩展元素的模式,扩展⼀个包就是将它分解为构成的元素,对每个元素应⽤模式,获得扩展后的列表。我们通过在模式的右边放⼀个省略号(…)来触发扩展操作。底层的实现细节如图所⽰。
递归逐渐展开参数包,最终用提前定义的不带参版本函数终止递归。
在这里插入图片描述

emplace系列接口(容器就地构造函数)

emplace接口使用可变模板参数,接收任意类型和数量的参数。
emplace对于不同参数的处理情况:

  1. 接收已经实例化的容器元素类型的对象,如果对象是左值会调用拷贝赋值,如果是右值会调用移动赋值插入
  2. 接收构造容器元素的参数包,一直将参数包传递至容器元素类型的构造函数时就地构造。

两种情况都离不开右值引用和完美转发(结合万能引用)保持原值属性
emplace_back是将容器中元素需要的构造参数打包传到底层,直接在容器的内存空间构造元素。push_back只能接收容器元素类型的对象,例如vector的push_back只能接收string不能接收string的构造参数。

与push系列对比:
push_back 只能接收 “成品” 元素,emplace_back 既可以接收的是 “原料”(构造参数)并在容器内 “现场制作” 元素,也可以接收“成品” 元素。在这里插入图片描述

emplace_back实现就地构造的三个重要条件

  1. 可变参数模板:emplace_back依赖于可变参数模板才能灵活地接收任意数量和类型的参数,在 C++11 之前,由于缺乏可变参数模板特性,函数参数的数量和类型必须在编译期固定,这就导致 push_back 只能设计为接收单个元素对象(或其引用)。
  2. 右值引用和完美转发:保持原值属性,使构造需要的参数能正确传递。
  3. 定位new:定位new允许在容器已分配的内存空间中直接构造对象(原地构造)在这里插入图片描述
    本质根据传入的参数包的类型调用对应构造函数:
    1.普通构造 -----参数包中是普通构造的参数
    2.拷贝构造 -----参数包是左值对象
    3.移动构造 -----参数包是右值对象

右值引用相关各种语法的总结

在这里插入图片描述

push右值对象

传递右值对象最终调用移动构造

  1. 右值引用:标记可转移资源对象,并能将其修改
  2. 完美转发<–万能引用<–引用折叠:实现左右值使用通用接口,传递过程中保持原值属性
  3. 移动语义:如果参数包是已经构造的右值对象,调用移动构造转移其资源。(如果是左值对象,调用拷贝构造)。在这里插入图片描述

emplace就地构造

就地构造:容器emplace接口将容器元素的构造函数参数包打包传递(过程中保存原值属性),最终在容器空间内部构造,避免产生临时对象。
相关语法:

  1. 可变参数模板:将所有参数打包传递
  2. {}初始化列表:在这里插入图片描述
  3. 右值引用:标记可转移资源对象,并能将其修改
  4. 完美转发<–万能引用<–引用折叠:实现左右值使用通用接口,传递过程中保持原值属性
  5. 移动语义:如果参数包是已经构造的右值对象,调用移动构造转移其资源。(如果是左值对象,调用拷贝构造)。在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值