0

0

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

高洛峰

高洛峰

发布时间:2017-01-23 14:11:24

|

1765人浏览过

|

来源于php中文网

原创

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();

   

XPaper Ai
XPaper Ai

AI撰写论文、开题报告生成、AI论文生成器尽在XPaper Ai论文写作辅助指导平台

下载

立即学习C++免费学习笔记(深入)”;

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

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

2. 右值引用:

    右值引用的表示方法为

Datatype&& variable

   

立即学习C++免费学习笔记(深入)”;

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

(Demo是一个类)
Demo foo(){
  Demo tmp;
  return tmp;
}

   

立即学习C++免费学习笔记(深入)”;

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

Demo x=foo();

   

立即学习C++免费学习笔记(深入)”;

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

Demo&& x=foo();

   

立即学习C++免费学习笔记(深入)”;

那么就不需要进行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;
 
}

   

立即学习C++免费学习笔记(深入)”;

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

Demo(Demo&& lre):arr(lre.arr),size(lre.size)({lre.arr=NULL;}

   

立即学习C++免费学习笔记(深入)”;

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

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

4. move()函数

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

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

5. 完美转发(perfect forwarding)

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

void func(const int);
void func(int);
void func(int&&);

   

立即学习C++免费学习笔记(深入)”;

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

void cover(typename para){
  func(para);
}

   

立即学习C++免费学习笔记(深入)”;

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

template
void cover(T para){
  ...
  func(para);
  ...
}

   

立即学习C++免费学习笔记(深入)”;

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

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

T&               A&                A&
T&               A&&              A&
T&&             A&                A&
T&&             A&&              A&&

 因此,对于前例的函数包装要求,采用以下模板就可以解决:

template
void cover(T&& para){
  ...
  func(static_cast(para));
  ...
}

如果传入的是左值引用,转发函数将被实例化为:

void func(T& && para){
 
  func(static_cast(para));
 
}

应用引用折叠,就为:

void func(T& para){
 
  func(static_cast(para));
 
}

如果传入的是右值引用,转发函数将被实例化为:

void func(T&& &¶){
 
   func(static_cast(para));
}

应用引用折叠,就是:

void func(T&& para){
 
  func(static_cast(para));
 
}

对于以上的static_cast ,实际上只在para被推导为右值引用的时候才发挥作用,由于para是左值(右值引用是左值),因此需要将它转为右值后再传入func内,C++ 11在定义了一个std::forward函数来实现以上行为,

所以最终版本

template
 
void cover(T&& para){
 
  func(forward(forward(para)));
 
}

std::forward的实现与static_cast(para)稍有不同

std::forward函数的用法为forward(para) , 若T为左值引用,para将被转换为T类型的左值,否则para将被转换为T类型右值

总结

以上就是关于C++11中右值引用、转移语义和完美转发的全部内容,这篇文章介绍的很详细,希望对大家的学习工作能有所帮助。

更多浅析C++11中的右值引用、转移语义和完美转发相关文章请关注PHP中文网!

c++速学教程(入门到精通)
c++速学教程(入门到精通)

c++怎么学习?c++怎么入门?c++在哪学?c++怎么学才快?不用担心,这里为大家提供了c++速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
俄罗斯Yandex引擎入口
俄罗斯Yandex引擎入口

2026年俄罗斯Yandex搜索引擎最新入口汇总,涵盖免登录、多语言支持、无广告视频播放及本地化服务等核心功能。阅读专题下面的文章了解更多详细内容。

178

2026.01.28

包子漫画在线官方入口大全
包子漫画在线官方入口大全

本合集汇总了包子漫画2026最新官方在线观看入口,涵盖备用域名、正版无广告链接及多端适配地址,助你畅享12700+高清漫画资源。阅读专题下面的文章了解更多详细内容。

35

2026.01.28

ao3中文版官网地址大全
ao3中文版官网地址大全

AO3最新中文版官网入口合集,汇总2026年主站及国内优化镜像链接,支持简体中文界面、无广告阅读与多设备同步。阅读专题下面的文章了解更多详细内容。

79

2026.01.28

php怎么写接口教程
php怎么写接口教程

本合集涵盖PHP接口开发基础、RESTful API设计、数据交互与安全处理等实用教程,助你快速掌握PHP接口编写技巧。阅读专题下面的文章了解更多详细内容。

2

2026.01.28

php中文乱码如何解决
php中文乱码如何解决

本文整理了php中文乱码如何解决及解决方法,阅读节专题下面的文章了解更多详细内容。

4

2026.01.28

Java 消息队列与异步架构实战
Java 消息队列与异步架构实战

本专题系统讲解 Java 在消息队列与异步系统架构中的核心应用,涵盖消息队列基本原理、Kafka 与 RabbitMQ 的使用场景对比、生产者与消费者模型、消息可靠性与顺序性保障、重复消费与幂等处理,以及在高并发系统中的异步解耦设计。通过实战案例,帮助学习者掌握 使用 Java 构建高吞吐、高可靠异步消息系统的完整思路。

8

2026.01.28

Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

24

2026.01.27

拼多多赚钱的5种方法 拼多多赚钱的5种方法
拼多多赚钱的5种方法 拼多多赚钱的5种方法

在拼多多上赚钱主要可以通过无货源模式一件代发、精细化运营特色店铺、参与官方高流量活动、利用拼团机制社交裂变,以及成为多多进宝推广员这5种方法实现。核心策略在于通过低成本、高效率的供应链管理与营销,利用平台社交电商红利实现盈利。

122

2026.01.26

edge浏览器怎样设置主页 edge浏览器自定义设置教程
edge浏览器怎样设置主页 edge浏览器自定义设置教程

在Edge浏览器中设置主页,请依次点击右上角“...”图标 > 设置 > 开始、主页和新建标签页。在“Microsoft Edge 启动时”选择“打开以下页面”,点击“添加新页面”并输入网址。若要使用主页按钮,需在“外观”设置中开启“显示主页按钮”并设定网址。

72

2026.01.26

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号