• linkedu视频
  • 平面设计
  • 电脑入门
  • 操作系统
  • 办公应用
  • 电脑硬件
  • 动画设计
  • 3D设计
  • 网页设计
  • CAD设计
  • 影音处理
  • 数据库
  • 程序设计
  • 认证考试
  • 信息管理
  • 信息安全
菜单
linkedu.com
  • 网页制作
  • 数据库
  • 程序设计
  • 操作系统
  • CMS教程
  • 游戏攻略
  • 脚本语言
  • 平面设计
  • 软件教程
  • 网络安全
  • 电脑知识
  • 服务器
  • 视频教程
  • JavaScript
  • ASP.NET
  • PHP
  • 正则表达式
  • AJAX
  • JSP
  • ASP
  • Flex
  • XML
  • 编程技巧
  • Android
  • swift
  • C#教程
  • vb
  • vb.net
  • C语言
  • Java
  • Delphi
  • 易语言
  • vc/mfc
  • 嵌入式开发
  • 游戏开发
  • ios
  • 编程问答
  • 汇编语言
  • 微信小程序
  • 数据结构
  • OpenGL
  • 架构设计
  • qt
  • 微信公众号
您的位置:首页 > 程序设计 >C语言 > 浅析C++11中的右值引用、转移语义和完美转发

浅析C++11中的右值引用、转移语义和完美转发

作者: 字体:[增加 减小] 来源:互联网 时间:2017-05-28

通过本文主要向大家介绍了语义,语义分析,语义学,语义网,语义场等相关知识,希望对您有所帮助,也希望大家支持linkedu.com www.linkedu.com

1. 左值与右值:

    C++对于左值和右值没有标准定义,但是有一个被广泛认同的说法:可以取地址的,有名字的,非临时的就是左值;不能取地址的,没有名字的,临时的就是右值.

    可见立即数,函数返回的值等都是右值;而非匿名对象(包括变量),函数返回的引用,const对象等都是左值.

    从本质上理解,创建和销毁由编译器幕后控制的,程序员只能确保在本行代码有效的,就是右值(包括立即数);而用户创建的,通过作用域规则可知其生存期的,就是左值(包括函数返回的局部变量的引用以及const对象),例如:

int& foo(){int tmp; return tmp;}

int fooo(){int tmp; return tmp;}

int a=10;

const int b;

int& temp=foo();//虽然合法,但temp引用了一个已经不存在的对象

int tempp=fooo();
</div>

以上代码中,a,temp和foo()都是非常量左值,b是常量左值,fooo()是非常量右值,10是常量右值,有一点要特别注意:返回的引用是左值(可以取地址)!

一般来说,编译器是不允许对右值进行更改的(因为右值的生存期不由程序员掌握,即使更改了右值也未必可以用),对于内置类型对象尤其如此,但C++允许使用右值对象调用成员函数,虽然允许这样做,但出于同样原因,最好不要这么做.

2. 右值引用:

    右值引用的表示方法为

 Datatype&& variable
</div>

    右值引用是C++ 11新增的特性,所以C++ 98的引用为左值引用.右值引用用来绑定到右值,绑定到右值以后本来会被销毁的右值的生存期会延长至与绑定到它的右值引用的生存期,右值引用的存在并不是为了取代左值引用,而是充分利用右值(特别是临时对象)的建构来减少对象建构和析构操作以达到提高效率的目的,例如对于以下函数:

(Demo是一个类)
Demo foo(){ 
  Demo tmp;
  return tmp;
}
</div>

在编译器不进行RVO(return value optimization)优化的前提下以下操作:

Demo x=foo();
</div>

将会调用三次构造函数(tmp的,x的,临时对象的),相应的在对象被销毁时也会调用三次析构函数,而如果采用右值引用的方式:

Demo&& x=foo();
</div>

那么就不需要进行x的建构,本来本来要被销毁的临时对象也会由于x的绑定而将生存期延长至和x一样(可以理解为x赋予了那个临时对象一个合法地位:一个名字),就需要提高了效率(代价就是tmp需要占据4字节空间,但这是微不足道的).

    右值引用与左值引用绑定规则:

         常量左值引用可以绑定到常量和非常量左值,常量和非常量右值;

         非常量左值引用只能绑定到非常量左值;

         非常量右值引用只能绑定到非常量右值(vs2013也可以绑定到常量右值);

         常量右值引用只能绑定到常量和非常量右值(非常量右值引用只是为了语义的完整而存在,常量左值引用就可以实现它的作用).

         虽然从绑定规则中可以看出常量左值引用也可以绑定到右值,但显然不可以改变右值的值,右值引用就可以,从而实现转移语义,因为右值引用通常要改变所绑定的右值,所以被绑定的右值不能为const.

    注意:右值引用是左值!

3. 转移语义(move semantics):

    右值引用被引入的目的之一就是实现转移语义,转移语义可以将资源 ( 堆,系统对象等 ) 的所有权从一个对象(通常是匿名的临时对象)转移到另一个对象,从而减少对象构建及销毁操作,提高程序效率(这在2的例子中已经作了解释).转移语义与拷贝语义是相对的.从转移语义可以看出,实际上,转移语义并不是新的概念,它实际上已经在C++98/03的语言和库中被使用了,比如在某些情况下拷贝构造函数的省略(copy constructor elision in some contexts),智能指针的拷贝(auto_ptr “copy”),链表拼接(list::splice)和容器内的置换(swap on containers)等,只是还没有统一的语法和语义支持

    虽然普通的函数和操作符也可以利用右值引用实现转移语义(如2中的例子),但转移语义通常是通过转移构造函数和转移赋值操作符实现的.转移构造函数的原型为Classname(Typename&&) ,而拷贝构造函数的原型为Classname(const Typename&) ,转移构造函数不会被编译器自动生成,需要自己定义,只定义转移构造函数也不影响编译器生成拷贝构造函数,如果传递的参数是左值,就调用拷贝构造函数,反之,就调用转移构造函数.

例如:

class Demo{

public:

  Demo():p(new int[10000]{};

  Demo(Demo&& lre):arr(lre.arr),size(lra.size){lre.arr=NULL;}//转移构造函数

  Demo(const Demo& lre):arr(new int[10000]),size(arr.size){

    for(int cou=0;cou<10000;++cou)

      arr[cou]=lew.arr[cou];

  }

private:

  int size;

  int* arr;

}
</div>

    从以上代码可以看出,拷贝构造函数在堆中重新开辟了一个大小为10000的int型数组,然后每个元素分别拷贝,而转移构造函数则是直接接管参数的指针所指向的资源,效率搞下立判!需要注意的是转移构造函数实参必须是右值,一般是临时对象,如函数的返回值等,对于此类临时对象一般在当行代码之后就被销毁,而采用转移构造函数可以延长其生命期,可谓是物尽其用,同时有避免了重新开辟数组.对于上述代码中的转移构造函数,有必要详细分析一下:

Demo(Demo&& lre):arr(lre.arr),size(lre.size)({lre.arr=NULL;}
</div>

lre是一个右值引用,通过它间接访问实参(临时对象)的资源来完成资源转移,lre绑定的对象(必须)是右值,但lre本身是左值;

因为lre是函数的局部对象,”lre.arr=NULL"必不可少,否则函数结尾调用析构函数销毁lre时仍然会将资源释放,转移的资源还是被系统收回.

4. move()函数

    3中的例子并非万能,Demo(Demo&& lre)的实参必须是右值,有时候一个左值即将到达生存期,但是仍然想要使用转移语义接管它的资源,这时就需要move函数.

    std::move函数定义在标准库<utility>中,它的作用是将左值强行转化为右值使用,从实现上讲,std:move等同于static_cast<T&&>(lvalue) ,由此看出,被转化的左值本身的生存期和左值属性并没有被改变,这类似于const_cast函数.因此被move的实参应该是即将到达生存期的左值,否则的话可能起到反面效果.

5. 完美转发(perfect forwarding)

    完美转发指的是将一组实参"完美"地传递给形参,完美指的是参数的const属性与左右值属性不变,例如在进行函数包装的时候,func函数存在下列重载:

void func(const int);
void func(int);
void func(int&&);
</div>

如果要将它们包装到一个函数cover内,以实现:

void cover(typename para){
  func(para);
}
</div>

使得针对不同实参能在cover内调用相应类型的函数,似乎只能通过对cover进行函数重载,这使代码变得冗繁,另一种方法就是使用函数模板,但在C++ 11之前,实现该功能的函数模板只能采用值传递,如下:

template<typename T>
void cover(T para){
  ...
  func(para);
  ...
}
</div>

但如果传递的是一个相当大的对象,又会造成效率问题,要通过引用传递实现形参与实参的完美匹配(包裹const属性与左右值属性的完美匹配),就要使用C++ 11 新引入的引用折叠规则:

函数形参       T的类型         推导后的函数形参

T&               A&                A&
T&               A&&    &nb

分享到:QQ空间新浪微博腾讯微博微信百度贴吧QQ好友复制网址打印

您可能想查找下面的文章:

  • 浅析C++11中的右值引用、转移语义和完美转发

相关文章

  • 2017-05-28关于大小端、位域的一些概念详解
  • 2017-05-28C语言实现的一个万年历小程序
  • 2017-05-28在C语言中调用C++做的动态链接库
  • 2017-05-28算法学习入门之使用C语言实现各大基本的排序算法
  • 2017-05-28基于Windows API实现遍历所有文件并删除的方法
  • 2017-05-28vector, list, map在遍历时删除符合条件的元素实现方法
  • 2017-05-28C++循环队列实现模型
  • 2017-05-28浅谈十进制小数和二进制小数之间的转换
  • 2017-05-28ipv6实现tcp编程示例
  • 2017-09-12求一个n阶方阵对角线元素之和。

文章分类

  • JavaScript
  • ASP.NET
  • PHP
  • 正则表达式
  • AJAX
  • JSP
  • ASP
  • Flex
  • XML
  • 编程技巧
  • Android
  • swift
  • C#教程
  • vb
  • vb.net
  • C语言
  • Java
  • Delphi
  • 易语言
  • vc/mfc
  • 嵌入式开发
  • 游戏开发
  • ios
  • 编程问答
  • 汇编语言
  • 微信小程序
  • 数据结构
  • OpenGL
  • 架构设计
  • qt
  • 微信公众号

最近更新的内容

    • C 转移表/转换表的深入分析
    • 用标准c++实现string与各种类型之间的转换
    • 基于c语言中调试工具的用法汇总(不包含gdb)
    • C++基于hook iat改变Messagebox实例
    • 浅谈VC++中的内联
    • C语言实现两个递减数列中寻找某一个数
    • C++ COM编程之QueryInterface函数(二)
    • 基于大端法、小端法以及网络字节序的深入理解
    • C语言指针变量的定义和使用(精华)
    • C字符串与C++中string的区别详解

关于我们 - 联系我们 - 免责声明 - 网站地图

©2020-2025 All Rights Reserved. linkedu.com 版权所有