C++11之引用

news2024/11/16 6:30:08

文章目录

    • 目的
    • 为啥要引入右值引用
    • 什么是右值引用
    • 右值引用作用
      • 移动构造函数
      • 移动语义 std::move
      • 移动语义注意事项
      • 完美转发
    • 博客

目的

了解对应左值引用, 右值引用,移动语义, 完美转发含义。

右值引用(及其支持的移动语义Move semantics和完美转发Perfect forwarding)是C++11中加入的最重大语言特性之一。

右值引用

我希望这篇博客能让我或者读者能够明白如下三点, 那么这篇博客就有意义了。

1. 为什么要引入右值引用这个概念呢?
2. 什么是右值引用
3. 右值引用作用

先引入左值, 右值概念:

   一般化左值(glvalue): 可寻址的表达式,即可使用&操作符的表达式
   纯右值(prvalue): 只读表达式,即不可使用&操作符的表达式,一般为临时值
   xvalue: 延长了生命周期的表达式,即右值引用
        lvalue :等号左边的值称为左值
        rvalue :等号右边的值称为右值
  左值右值是指一个表达式(当然这个表达式可以仅是一个简单的变量),区分左值和右值是看表达式能否使用&操作符。
  左值引用和右值引用是指左值或者右值的型别,左值的型别可以为左值引用,也可以为右值引用。右值的型别可以为右值引用,但不能为左值引用,因为左值引用仅能引用左值(const修饰的左值引用可以引用右值,因为函数参数重const修饰的参数,编译器会为传入的右值创建临时变量,所以const修饰的左值引用其实是引用的这个临时变量)。右值引用仅能引用纯右值。

为啥要引入右值引用

在C++98中,临时量(术语为右值,因其出现在赋值表达式的右边)可以被传给函数,但只能被接受为const &类型。
这样函数便无法区分传给const &的是真实的右值还是常规变量。而且,由于类型为const &,函数也无法改变所传对象的值。
C++0x将增加一种名为右值引用的新的引用类型,记作typename &&。这种类型可以被接受为非const值,从而允许改变其值。这种改变将允许某些对象创建转移语义。比如,一个std::vector,就其内部实现而言,是一个C式数组的封装。如果需要创建vector临时量或者从函数中返回vector,那就只能通过创建一个新的vector并拷贝所有存于右值中的数据来存储数据。之后这个临时的vector则会被销毁,同时删除其包含的数据。有了右值引用,一个参数为指向某个vector的右值引用的std::vector的转移构造器就能够简单地将该右值中C式数组的指针复制到新的vector,然后将该右值清空。这里没有数组拷贝,并且销毁被清空的右值也不会销毁保存数据的内存。返回vector的函数现在只需要返回一个std::vector<>&&。如果vector没有转移构造器,那么结果会像以前一样:用std::vector<> &参数调用它的拷贝构造器。如果vector确实具有转移构造器,那么转移构造器就会被调用,从而避免大量的内存分配。

eg:

std::move();
bool is_r_value(int &&)
{
    return true;
}
bool is_r_value(const int &)
{
    return false;
}
void test(int &&i)
{
    is_r_value(i); // false
    is_r_value(std::move(i)); // true
}

什么是右值引用

最直观理解方法:绑定到右值的引用,哪怕右值是一个临时变量, 只不过让其生命周期变长而已,但是它本身却不能绑定任何左值。

右值引用作用

对于返回右值引用的函数来说,支持右值声明的绑定,不支持非常量左值,却支持非常量左值
引用类型
右值引用

1: 右值引用无论作为参数还是返回值,都可以使用临时变量,并且由于其可以窃取临时变量中的内存,导致其效率较高;
2: 常量左值引用是万能类型,当参数是常量左值时,我们传入右值也可以;当返回值是右值时,使用常量左值也可以接收。
3: 左值引用无论是作为参数还是返回值,都要求其不能使用临时变量。
4:当右值引用作为构造函数参数时,这就是所谓的移动构造函数,也就是所谓的移动语义。

当成员存在指针成员,使用复制拷贝构造函数, 需要进行深拷贝。

snippets. 1:

QString s;
QString p = s;

上面无疑是需要深拷贝的,因为无论s,还是p,都可能在我们后面的代码里面继续用到。

snippets. 2:

QString GetTemp() {return QString("Hello World!");}
int main()
{
	QString str = GetTemp();
}

这里代码中实际只用到了str,但是实际上却调用了一次构造(GetTemp函数中调用String构造生成临时对象)、两次拷贝构造(一次是GetTemp函数调用拷贝构造生成临时对象用于返回、一次是str接收)、三次析构。这里拷贝构造调用了两次深拷贝,但是最后实际使用到的对象却只有str,因此,可以看出,这里有一次深拷贝是多余的。

当堆内存很大时,多余的深拷贝以及其对象的堆内存析构耗时就会变的很可观,那么是否有一种方式,可以让函数中的返回的临时对象空间是否可以不析构,而可以重用呢?

基于上述原因,因此c++11提供了移动构造来解决上述问题。移动构造也是基于右值引用来实现的。

移动构造函数

class MyClass{
public:
	MyClass():d(new int(3)){
	}
	MyClass(const MyClass& h) : d(new int(*h.d)){
	}
	######move constructor
	MyClass(MyClass&& h) : d(h.d){   
	   h.d = nullptr;
	}
private:
	int *d = nullptr;
}

MyClass GetTempClass() {
	MyClass myclass;
	return myclass;
}

int main()
{
	MyClass a = GetTempClass();
	...
}

######move constructor 表示构造函数, 它与拷贝构造函数不同的是,它接收的是一个右值引用的参数,即MyClass && h,移动构造函数使用参数h的成员d初始化了本对象的成员d初始化了本对象的成员d(而不是像构造函数一样需要分配内存,然后再将内容一次拷贝到新分配的内存中),而h的成员d随后就被置空。

这里的“偷”堆内存,就是指将对象d指向h.d所指的内存这一条,除此之外,我们还要讲h的d置为空指针,这是因为再移动构造以后,临时对象会被析构,如果不改变h.d的指向的话,那么我们“偷”来的堆内存也被析构掉了。

那么移动构造函数什么时候才会被触发呢?事实上,我们也提供了拷贝构造函数,从外部调用形式来看,拷贝构造及移动构造调用没有分别,那么怎么确保我们调用的是移动构造呢?这就涉及到临时对象的问题。这里涉及到移动语义了。

移动语义 std::move

std::move主要用于将左值强行转换为右值,需要注意的是,被转化的左值生命周期并没有因这种转换而改变。但是在使用std::move时,我们却需要注意:一旦该左值被转换为右值,如果和移动语义结合使用,那么该左值的生命周期就将结束,如果此后还继续使用改左值,那么就会出现严重错误。

class MyClass{
public:
	MyClass():d(new int(3)){
	}
	MyClass(const MyClass& h) : d(new int(*h.d)){
	}
	MyClass(MyClass&& h) : d(h.d){   
	   h.d = nullptr;
	}
private:
	int *d = nullptr;
}

int main()
{
	MyClass a;
	MyClass c(move(a));
	...
}

如上式中,a由于移动语义,其堆内存实际已被释放,后面继续调用,那么就会报错。
基于此,所以我们应该注意:应当确保使用std::move用于移动语义的变量是一个临时量。下面是把std::move用于移动语义的正确姿势:

Class Moveable {
public:
	...
	Moveable (Moveable &&m) :
		i(m.i) ,h(move(m.h)){ //#1
		m.i = nullptr;
	}
	
int *i;
MyClass h;
}
Moveable GetTemp(){return Moveable();}

Moveable a(GetTemp());

分析上述代码可以发现,GetTemp()临时对象将很快析构,可以避免出现错误。
  
这里考虑一下,如果#1所在的地方HugeMem不支持移动语义怎么办,这也没多大问题,因为此时会调用其常量左值拷贝函数(上文中已经说明了常量左值是接收右值的),因此也不会有多大问题。基于此,因此我们在编写移动构造函数时应总是将拥有堆内存、文件句柄的资源从左值转换为右值。

移动语义与std::move结合时,要格外注意不要误用,下面是一个错误使用的示例:

int main()
{
    Moveable a;
    Moveable c(move(a)); 
    cout << *a.i << endl;
    return 0;
}

a本身是一个左值,但是被move强转为右值,但是a的生命周期又还没有结束,根据上述移动语义的说明,我们可知:a指向i的内存已经被c窃取了,a.i指针指向空,那么一旦输出i的值,那么程序就会出现错误。
   从上面示例我们可以得到一个注意事项,即:我们在使用move语义时,一定要确保被强转的左值很快会被析构,否则就会带来隐患。

移动语义注意事项

移动构造函数中要避免使用const右值引用,因为我们最终是要修改右值引用中堆内存指向的。
C++11中,实际拷贝/移动构造函数有以下三个版本:
    T Object(T &)
    T Object(const T &)
    T Object(T &&)
  一般来说,编译器会隐式的生成一个移动构造函数,不过如果我们自己声明了自定义的拷贝构造函数、拷贝赋值函数、移动赋值函数、析构函数中的一个或多个,那么编译器都不会再生成默认版本。默认版本的移动构造一般也是按位拷贝,这对实现移动语义来说是不够的,通常情况下,如果要实现移动语义,都需要我们自定义移动构造函数。当然,如果类中不包含堆内存,实不实现移动语义都不重要。
  考虑到常量的左值引用是万能的,假设我们传入参数类型为右值,但是又没有实现移动语义会怎么样呢?那么就会进入常量拷贝构造函数,这就确保了即使移动构造不成,还可以拷贝。

移动语义的swap

template<class T>
void swap(T& a,T& b)
{
	T tmp(move(a));
	a = move(b);
	b = move(tmp);
}

上述代码完全避免了资源的释放与申请,从而完成高效置换。

完美转发

所谓完美转发,就是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另一个函数。由于拷贝问题的存在,所以完美转发一般不包括值传递。
如何确定转发函数的提供的实际类型呢?

template<typename T>
void IamForwording(T &&t){ IrunCodeActually(t); }
T a;
IamForwording(a);//a是左值,而转发函数参数又是右值,此时目标函数IrunCodeActually中的是左值还是右值?

基于上述原因,所以c++11提供了引用折叠,引用折叠一方面确定了左值右值类型叠加时的类型确定规则,另一方面该规则确保了转发者与接收者的类型一致。

可以用两条语句来抽象表示转发者与接收者的参数类型叠加问题:

typedef T& TR;
TR& v;

C++11定义了以下的引用折叠规则:
在这里插入图片描述
 我们可以把TR认为是转发函数参数类型,v为接收函数类型,v的实际类型为叠加后的类型。从上表可以看出一旦定义中出现了左值引用,那么引用这得优先将其折叠为左值引用。

从上表也可以看出,除了第5种(TR 为 T&&,v为TR&, 而v实际为A&)类型外,其他都是不需要进行额外转发就能够确保模板参数类型TR和目标函数参数类型v一致。可见引用折叠规则独自无法完成完美转发。因此,C++11在此基础上又提出了std::forward。
  
 分析上表第5种情况可以看出,实际类型为A&,但是我们传入的是T&&,要确保目标函数也收到T&&,那么就只能A&转换为T&&,很明显,这是左右值的转换,我们很自然想起了std::move,但是c++11为了在功能上区别完美转发,所以使用std::forward取代std::move。

void RunCode(int && m) {}
void RunCode(int &m) {}
void RunCode(const int && m) {}
void RunCode(const int & m) {}

template<typename T>
void PerfectForward(T &&t){RunCode(forward<T>(t));}

int main()
{
    int a;
    int b;
    const int c = 1;
    const int d = 0;

    PerfectForward(a);  // lvalue ref
    PerfectForward(move(b));  // rvalue ref
    PerfectForward(c);  // const lvalue ref
    PerfectForward(move(d)); // const rvalue ref
}

从上面代码种可以看到,当模板类型为左值时,其进入了目标函数的左值版本,当模板类型为右值时,其进入了目标函数的右值版本,转发函数可以视作不存在,这就是完美转发。

博客

博客一
博客二

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/78577.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

1562_AURIX_TC275_电源监控

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) 这一次的学习笔记内容比较少&#xff0c;因为有几页的文档内容跟之前看过的DataSheet内容雷同。因此&#xff0c;相应的学习笔记不再整理。 之前的学习笔记&#xff1a; (56条消息) 1451_…

Python学习基础笔记四十——os模块

os模块是与操作系统交互的一个接口。 os的方法Linux命令备注os.getcwd()pwd获取当前工作目录路径os.chdir()cd切换当前工作目录os.makedirs(dirname1/dirname2)mkdir -p dirname1/dirname2生成多级目录os.removedirs(dirname1)rmdir删除多级目录os.mkdir(dirname)mkdir dirnam…

JAVA入门零基础小白教程day04-数组

day04_java基础 课程目标 1. 【掌握】 IDEA的基本使用 2. 【理解】 什么是数组 3. 【掌握】 数组的定义及初始化 4. 【理解】 数组的内存图 6. 【理解】 数组常见的问题 7. 【掌握】 数组的案例 8. 【理解】 二维数组开发工具 一维数组 什么是数组 数组就是存储数据长度固定…

【Linux】源码安装Apache、Mysql、PHP以及LAMP部署验证

文章目录源码安装相关理论源代码安装特点源码包安装步骤一、源码安装Apache1、编译安装依赖包 apr2、编译安装依赖包 apr-util3、编译安装依赖包 pcre4、编译安装 Apache5、重启 apache 服务6、修改网页显示内容7、访问测试二、源码安装Mysql1、把系统自带的 boost 库卸载&…

【mmdetection系列】mmdetection之loss讲解

目录 1.configs 2.具体实现 3.调用 3.1 注册 3.2 调用 配置部分在configs/_base_/models目录下&#xff0c;具体实现在mmdet/models/loss目录下。 1.configs 有的时候写在head中作为参数&#xff0c;有的时候head内部进行默认调用。 我们以为例&#xff08;这里没有直接…

linux timer浅析

linux timer 1、数据结构 1.1 timer_list struct timer_list {struct hlist_node entry;unsigned long expires;void (*function)(struct timer_list *);u32 flags;#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map; #endif };entry:定时器保存到哈希表中的节点&am…

QT+Python停车场车牌识别计费管理系统

程序示例精选 Python停车场车牌识别计费管理系统 如需安装运行环境或远程调试&#xff0c;见文章底部微信名片&#xff01; 前言 QTPython是非常经典的窗体编程组合&#xff0c;功能完善&#xff0c;可视化界面美观易维护&#xff0c;这篇博客针对停车场车牌识别计费方面编写代…

JavaScript前端实用的工具函数封装

这篇文章主要为大家介绍了JavaScript前端实用的一些工具函数的封装&#xff0c;有需要的朋友可以借鉴参考下&#xff0c;希望能够有所帮助! 1.webpack里面配置自动注册组件 第一个参数是匹配路径,第二个是深度匹配,第三个是匹配规则 const requireComponent require.contex…

20-Django REST framework-Serializer序列化器

Serializer序列化器前言序列化器作用定义Serializer定义方法字段与选项创建Serializer对象序列化使用基本使用增加额外字段关联对象序列化反序列使用模型类序列化器ModelSerializer指定字段前言 本篇来学习Serializer序列化器知识 序列化器作用 进行数据的校验对数据对象进行…

[附源码]计算机毕业设计基于VUE的网上订餐系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【代码审计-JAVA】基于javaweb框架开发的

目录 一、javaweb三大框架 1、Spring&#xff08;开源分层的框架&#xff09; 2、Struts&#xff08;MVC设计模式&#xff09; 3、Hibernate&#xff08;开源的对象关系映射框架&#xff09; 二、特征 1、结构 2、Servlet 三、重要文件 1、web.xml 2、pom.xml 3、web…

【文献研究】班轮联盟下合作博弈的概念

前言&#xff1a;以下是本人做学术研究时搜集整理的资料&#xff0c;供有相同研究需求的人员参考。 1. 合作博弈的一些概念 合作博弃中比较重要的问题是共赢状态下的利润分配问题&#xff0c;这关系到联盟的合作机制能否长期有效。这里首先介绍几个重要的概念&#xff1a; &…

174.Django中文件上传和下载

1. 文件上传和下载环境搭建 创建django项目和子应用urls中包含子应用&#xff0c;在子应用中创建urls.py配置数据库sqlite3&#xff08;默认就是&#xff0c;无需配置&#xff09;配置settings&#xff0c;上传文件目录编写模型代码&#xff08;下面给出&#xff09;模型的预迁…

如何使用Java获取货币符号?

1. 前言 最近做了一个支付相关的需求&#xff0c;要求在收银台页面显示商品的价格时带上货币符号&#xffe5;&#xff0c;类似下图中的格式&#xff1a; 最初我是用的下面这样的代码&#xff1a; System.out.println(Currency.getInstance(Locale.CHINA).getSymbol());本机测…

postgresql_internals-14 学习笔记(一)

梳理一下之前理解不太清楚的知识点&#xff0c;重点内容可能会再拆出来单独研究。 原书链接&#xff1a;Index of / 一、 数据组织 1. pg系统库 template0&#xff1a;用于从逻辑备份还原&#xff0c;或创建不同字符集的数据库&#xff0c;不可以修改template1&#xff1a;真…

[附源码]Python计算机毕业设计SSM基于框架的毕业生就业管理系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Unity脚本 (1) --- 创建脚本以及挂载脚本的本质,脚本模板的修改

值类型 --- 在栈区中开辟内存空间并直接存储在栈区中&#xff0c;引用类型 --- 在栈区中开辟内存空间存引用&#xff0c;在堆区中开辟内存空间存数据&#xff08;有可能堆区中还要开辟引用&#xff09;&#xff0c;然后将堆区中存储数据的内存空间的地址传给引用接收 什么是脚本…

HTTP 请求走私

目录 0x01 简介 0x02 成因 2.1 Keep-Alive 2.2 Pipeline 2.3 Content-Length 2.4 Transfer-Encoding 0x03 分类 0x04. 攻击 4.1. CL不为0的GET请求 4.2 CL-CL 4.3 CL-TE 4.4 TE-CL 4.5. TE-TE 0x05 防御 参考资料&#xff1a; 0x01 简介 HTTP请求走私是一种干扰…

Mysql优化-全面详解(学习总结---从入门到深化)

Sql性能下降的原因 在程序的运行过程中&#xff0c;我们会发现这样的一个现象&#xff0c;随着程序运行 时间的不断推移以及数据量越来越大&#xff0c;程序响应的时间逐渐变慢&#xff0c; 程序变得卡顿&#xff0c;但最开始的时候并不是这样的&#xff0c;那是什么原因导致 的…

2023最新SSM计算机毕业设计选题大全(附源码+LW)之java毕业生回访系统564c4

最近发现近年来越来越多的人开始追求毕设题目的设创、和新颖性。以往的xx管理系统、xx校园系统都过时了。大多数人都不愿意做这类的系统了&#xff0c;确实&#xff0c;从有毕设开始就有人做了。但是很多人又不知道哪些毕设题目才算是新颖、创意。太老土的不想做&#xff0c;创…