C++11右值引用(移动构造、完美转发等)

news2024/9/23 7:24:59

在讲解移动构造和完美转发之前,我们需要先了解什么是右值引用。

但在讲解右值引用之前,我们也得知道左值和右值分别是什么,有什么区别。

目录

左值与右值

左值与左值引用

右值与右值引用

引用和右值引用的区别

移动构造

移动赋值

插入接口中的右值引用

完美转发


左值与右值

左值与右值是C语言中的概念,但C标准并没有给出严格的区分方式,一般认为:可以放在=左边或者能够取地址的称为左值,只能放在=右边的,或者不能取地址的称为右值但是也不一定完全正确。

左值与左值引用

左值是一个表示数据的表迭式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,也可以出现在赋值符号右边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。

例如:

   //左值,可以取它的地址
    //a,b,*p都是左值
    int a = 10;
    const int b = 20;
    int* p = &a;
    *p = 100;
    //这几个是对上面左值的引用
    int& rp = a;
    const int& rb = b;
    int*& rp = p;

例如左值可以出现在等号的右边

    //右值
    double x = 1.1, y = 2.2;
    10;
    x + y;
    fmin(x, y);
    return 0;

右值与右值引用

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址右值引用就是对右值的引用,给右值取别名
以下是一些右值:

    //右值
    double x = 1.1, y = 2.2;
    10;//字面常量
    x + y;//这个是个表达式,最终结果会存放在临时变量里,也是个右值
    fmin(x, y);//函数返回值
    return 0;
    //以下是对上面右值的引用
    int&& rr1 = 10;
    double&& rr2 = x + y;
    double&& rr3 = fmin(x + y);

但右值一定不能放到等号的左边:

    //错误:右值不能放在等号左边
    10 = 1;
    x + y = 1;
    fmin(x, y) = 1;

总结来说:左值和右值的主要区别是 是否能对这些值取地址。 能取地址是左值,不能取地址的是右值.

引用和右值引用的区别

 在C++98中的普通引用与const引用在引用实体上的区别:

左值引用可以引用左值,但引用右值必须加上const.

    //普通引用:可以引用左值
    int a = 10;
    int& ra = a;

    int& rra = 10;//编译出错,因为10是右值
    //加上const便可以引用右值.
    const int& ra2 = 10;
    const int& ra3 = a;

那么右值引用可以引用左值吗?

右值引用不可以引用左值,但可以引用move以后的左值.

    int c = 10;
    int&& rrc = c;//错误,右值引用不可引用左值
    int&& rrc2 = move(c);//正确,右值引用可以引用move以后的左值

需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址,也就是说例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1。如果不想rr1被修改,可以用const int&& r1去引用,是不是感觉很神奇,这个了解一下实际中右值引用的使用场景并不在于此,这个特性也不重要。

    int&& rr1 = 10;
    rr1 = 20;//可以对右值引用的值作修改
    cout << &rr1 << endl;//也可以获取地址
    const int&& rr2 = 10;
    //rr2 = 20;//错误,rr2被const去引用了,不可以再被修改
    cout << &rr2 << endl;

可以看到rr1和rr2的地址也成输出出来。

那么右值引用到底是用来干什么的呢?

先不说右值引用,就先说引用的价值是什么? 减少拷贝!

在有些函数中参数加了引用,便不必再拷贝一份新的了。

左值引用解决哪些问题:

1.做参数:a.减少拷贝,提高效率  b.做输出型参数

2.做返回值:a.减少拷贝,提高效率  b.引用返回,可以修改返回对象(operator[]).

但左值引用对于以下场景会很难处理

string to_string(int val);

它的内部一定是返回了一个string,这个时候如果利用左值引用,成为下面这样:

string& to_string(int val);

这样就肯定会报错,因为引用了之前函数里的返回值,而里面的返回值又是一个临时对象,出了作用域会被销毁,所以引用一个被清理的数据会直接报错了。

这种情况我们可以考虑输出型参数,即不再用返回值,改为以下这样:

string to_string(int val,string& ans);

这样最后我们直接用ans即可.但是这不太符合我们的使用习惯。按正常来说,应该是接收一下,而这个还需要我们传入一个参数作为结果,很不习惯。

C++右值引用就可以用来解决以上问题.

先来看下面一种现象:

 我们应该知道,函数的返回值返回时会把自身的值拷贝给一个临时变量,这个临时变量再拷贝给上一层栈帧所接收的那个值。

所以一共会拷贝两次。但是编译器一般会直接优化为一次,即直接将返回值拷贝给上一层栈帧所接收的那个值。(str直接拷贝给了ret)

 既然这样,那这个临时变量有什么意义呢?我们每次直接拷贝给最后接收的值不就行了吗?

 那肯定不行,如果不是一开始就接收返回值,而是先定义的然后再接收就不可以了:

这样就必须借助临时变量了,而且一共拷贝了两次(拷贝构造一次,=拷贝赋值运算符一次)

 了解了这个,那我们开始讲解移动构造。右值引用一般不是直接使用,而是结合着其它只是一起使用。

移动构造

这里右值也分为两种:

1.内置类型右值---纯右值

2.自定义类型右值 --- 将亡值

将亡值正如其名,将要消亡,像一些临时对象,匿名对象之类的,生命周期只有本身这么一行,出了之后消亡了。

放在之前,如果是深拷贝,我们还得老老实实的开空间,然后拷贝一份数据到这个对象里,即使生命周期很短。

那既然你是将亡值,反正都要走了,在临走之前,做一个交易,把你的资源转移给我然后再走。所以叫做移动构造.

看下面的代码,这样就能理解大概的含义了:

void swap(string& s)
{
    ::swap(_str, s._str);
    ::swap(_size, s._size);
    ::swap(_capacity, s._capacity);
}
// 拷贝构造
string(const string& s)
    :_str(nullptr)
{
    cout << "string(const string& s) -- 拷贝构造(深拷贝)" << endl;

    //string tmp(s._str);
    //swap(s);

    _str = new char[s._capacity + 1];
    strcpy(_str, s._str);
    _size = s._size;
    _capacity = s._capacity;
}

// 移动构造
string(string&& s)
    :_str(nullptr)
    , _size(0)
    , _capacity(0)
{
    cout << "string(string&& s) -- 资源转移" << endl;
    swap(s);
}
int main()
{
    bit::string str1("hello");
	bit::string str2(str1); //  拷贝构造
	bit::string str3(move(str1)); // 移动构造,想要变成右值需要加上move
}

我们调试,当我们执行完str2即拷贝构造这条语句时,我们发现s1的数据已经完整的拷贝一份给到s2了.

 这时候str1和str2都还在。

我们继续向下调试,当执行完str3这条移动构造语句时,不同的现象出现了:

 可以发现,str3把str1里的资源转移了。由于上面对str1加上了move.

所以也要谨慎使用move.

移动构造和拷贝构造函数的区别是:在传值的场景下,移动构造减少了拷贝次数

 上面是移动构造的工作,而普通构造函数会多拷贝一次:

 这样移动构造其实少了一次拷贝构造,

移动构造中我们为什么敢直接交换右值,而不用创建一份临时变量来保存?

因为它是个将亡值!马上就要没了,所以可以直接换,不影响下面的程序。

移动赋值

既然有了移动构造和拷贝构造,也存在移动赋值和拷贝赋值。

回到刚开始说的那种情况:

 在编译器没有优化的情况下,会发生两次拷贝,这是在处理左值的情况下。

如果是右值的话,它们会进行两次移动(移动构造,移动赋值)

 

 相比起来,右值的移动构造和移动赋值提高了效率。只进行了两次的资源转移。而左值的情况却整拷贝了两次资源!代价未免太大了.

以上才是右值引用的真正价值。不仅构造和赋值在用右值引用,插入接口也在用右值引用。

插入接口中的右值引用

 上面所使用的右值引用 解决的是传值返回这些类型对象的问题。

C++11中,STL容器都提供右值版本。

插入过程中,如果传递对象是右值对象,那么就会进行资源转移,减少拷贝.

int main()
{
	list<bit::string> lt;
	bit::string s1("1111");
	// 这里调用的是拷贝构造
	lt.push_back(s1);
	// 下面调用都是移动构造
	lt.push_back("2222");
	lt.push_back(std::move(s1));
	return 0;
} 

完美转发

模板中&&万能引用

  • 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
  • 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力 。

我们看以下代码,看看输出是不是符合我们的预期结果,注释的为预期结果。

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }

template<typename T>
void PerfectForward(T&& t)
{
    Fun(t);
}
int main()
{
    PerfectForward(10);      // 右值
    int a;
    PerfectForward(a);       // 左值
    PerfectForward(std::move(a)); // 右值
    const int b = 8;
    PerfectForward(b);    // const 左值
    PerfectForward(std::move(b)); // const 右值
    return 0;
}

输出结果:

我们发现,无论传入的是左值还是右值,最后都成为了左值。

这是因为右值引用的对象,再次传递时会退化成左值引用,其实这很好理解,上文我们提到过,右值一旦被引用就会被存在一个特定的地方并且可以取到地址,所以再次使用时编译器就会把它当成左值了。想要保持它的右值属性,就要使用完美转发。

 std::forward会在传参过程中保留数据的原生类型。

所以传参时加上forward,就可以保留其右值属性.

void PerfectForward(T&& t)
{
    Fun(std::forward<T>(t));
}

这个时候我们再次运行:

便符合我们的预期了. 

这样C++11中的右值引用就讲的差不多了。

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

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

相关文章

基于simulink处理监控视频以选择包含运动的帧(附源码)

一、前言 此示例演示如何处理监控视频以选择包含运动的帧。安全问题要求使用摄像机对重要位置进行持续监控。为了有效地记录、查看和存档这些海量数据&#xff0c;您可以减小视频帧大小或减少录制的视频帧总数。此示例说明了后一种方法。在其中&#xff0c;相机视野中的运动会…

1762_gcc编译c语言makefile自动生成工具的Perl实现

全部学习汇总&#xff1a; GreyZhang/g_makefile: Learn makefile from all kinds of tutorials on the web. Happy hacking and lets find an common way so we may dont need to touch makefile code any more! (github.com) 前阵子实现了一个CodeWarrior嵌入式开发环境的自动…

【Java从入门到大牛】数组详解

&#x1f525; 本文由 程序喵正在路上 原创&#xff0c;CSDN首发&#xff01; &#x1f496; 系列专栏&#xff1a;Java从入门到大牛 &#x1f320; 首发时间&#xff1a;2023年7月9日 &#x1f98b; 欢迎关注&#x1f5b1;点赞&#x1f44d;收藏&#x1f31f;留言&#x1f43e…

Python + pymysql 之 MySQL 查询操作

在MySQL中构建一个测试表&#xff0c;如下&#xff1a; 查询单条数据 # -*- coding: UTF-8 -*- import pymysqldef mysql_query():"""MySQL查询:return:"""# 打开数据库连接db pymysql.connect(host"127.0.0.1",user"root"…

IDEA 配置 openjdk,jre

使用 idea 配置 zip 版本的 openjdk 和 jre&#xff0c;我使用的时 redhat 提供的 jdk 和 jre。 一、下载 idea、jdk 和 jre 红帽下载地址为&#xff1a; jdk 和 jre 安装地址根据个人配置&#xff0c;我的配置如下&#xff1a; 下划线的分别为我的 jdk 和 jre 保存路径 二…

简单学习UE5.2的PCG框架插件里的基础概念

目标 最近了解到5.2版本推出了实验性的功能PCG框架&#xff0c;以及用它做的范例 Electric Dreams。 本篇我简单学习它的一些基础概念&#xff0c;做一些记录。 0. 启用插件 此功能相关的类型都在PCG插件中&#xff0c;因此首先要确保启用了PCG插件&#xff1a; 1. PCGGr…

为什么进程地址空间中包括操作系统?

今天聊聊进程地址空间这点小事。 说到进程的地址空间&#xff0c;大家可能都知道这样一张图&#xff1a; 这张图就是Linux程序运行起来后所谓的进程地址空间&#xff0c;这里包括我们熟悉的代码区、数据区、以及堆区和栈区&#xff0c;今天我们不讲解这些区域&#xff0c;而是…

Vue生态及实践 - SSR(上)

目录 目标 理论 Rendering 你真的需要SSR亦或是同构吗&#xff1f; 同构实践 通用代码 同构第一步&#xff1a;避免单例 src/app.js src/store.js src/router.js 同构第二步&#xff1a;Server entry【服务端进入】&#xff1b;Client entry【客户端进入】 src/entry…

ESP32设备驱动-AS5600磁性旋转位置传感器

AS5600磁性旋转位置传感器 文章目录 AS5600磁性旋转位置传感器1、AS5600介绍2、硬件准备3、软件准备4、驱动实现1、AS5600介绍 AS5600 是一款可编程的 12 位高分辨率非接触式磁性旋转位置传感器。 AS5600可以作为磁性电位器或磁性编码器使用,具有出色的可靠性和耐用性。 与传…

网页版代码编辑器实现

接着前几天写的博客https://blog.csdn.net/woyebuzhidao321/article/details/131495855&#xff0c;提到了涉及vscode网页版工作区创建的api&#xff0c;这两天一时兴起&#xff0c;搞了一个网页版的代码编辑器&#xff0c;如果在2020年10月之前&#xff0c;实现一个网页版代码…

活动笔记 | 「企业人效提升路径」之数字化实践

6月27日&#xff0c;由人力资源智享会联合盖雅工场等机构主办的2023中国人力资源数字化论坛在北京顺利举办。盖雅工场高级解决方案顾问谷天毅先生发表了主题为 《企业人效提升路径之数字化实践》 的分享。 以下是分享内容&#xff0c;enjoy~ △ 盖雅工场高级解决方案顾问谷天…

httpx 返回都是乱码问题,非编码问题。

因为python 的requests 不能使用抓http2 的报文。所以看了一些httpx的使用。但是发现httpx 不能自动解压&#xff0c;text打印出来的都是乱码。一开始以为是编码格式的bug &#xff0c;但是使用chardet 确认了确实是utf-8.然后怀疑是压缩的问题。先去官网搜了一些文档 文档说会…

opencv4.7.0编译opencv-contrib-4.7.0以及CUDA

0、引言 最近工作中需要用到使用CUDA加速后的opencv进行传统算法的开发&#xff0c;在编程之前&#xff0c;需要先解决环境编译和lib库问题&#xff0c;本文就是记录自己编译opencv-4.7.0的全过程。 1、CUDA下载和安装 可参考我之前的博客WIN10安装配置TensorRT详解中的前几…

达尔文——生物医疗科学领域大模型

赛灵力官网 1. 生物医疗领域的挑战 1.1 复杂性 生物系统和生物过程非常复杂&#xff0c;包含大量的相互作用和调控机制&#xff0c;理解和解析这些复杂性是一项巨大的挑战。 举例来说&#xff0c;单单一个人类&#xff0c;体内的生物信息就非常复杂&#xff1a; 人类体内体内…

Django_re_path_使用正则匹配url

与path定义的路由相比&#xff0c;re_path 定义的路由可以使用正则表达式匹配url。 需要注意的是&#xff1a; 如果未定义匹配结果的变量名&#xff0c;匹配的结果默认传入视图的第2个形参。如果定义了匹配结果的变量名&#xff0c;匹配的结果会传给视图的同名字段&#xff0…

从零开始学习自动驾驶决策规划

从零开始学习自动驾驶决策规划 从入门到掌握的一系列讲解&#xff0c;其中涵盖的内容如下&#xff1a; 前言课 第一节-ros工程的创建 第一节-运行环境和工程目录简介第二节-工程运行和小车模型搭建简介 第二节-车辆里程计第三节-整体架构思路 第三节-地图路线构建方法 第三节…

Packet Tracer – 配置静态 NAT

Packet Tracer – 配置静态 NAT 目标 第 1 部分&#xff1a;测试不使用 NAT 的访问 第 2 部分&#xff1a;配置静态 NAT 第 3 部分&#xff1a;测试使用 NAT 的访问 拓扑图 场景 在 IPv4 配置网络中&#xff0c;客户端和服务器使用专用编址。 然后&#xff0c;在含专用编址…

MATLAB---线性规划问题求最优解(含例题)

线性规划是运筹学的基础&#xff0c;在现实企业经营中&#xff0c;如何有效的利用有限的人力、财力、物力等资源。 MATLAB 为方便大家理解&#xff0c;这里我们直接用一个例题为大家讲解使用matlab求解线性规划问题。 根据上图给出的线性规划问题。我们使…

SpringBoot 如何使用 @ExceptionHandler 注解进行局部异常处理

SpringBoot 如何使用 ExceptionHandler 注解进行局部异常处理 介绍 在开发 Web 应用程序时&#xff0c;异常处理是非常重要的一部分。SpringBoot 提供了多种方式来处理异常&#xff0c;其中之一是使用 ExceptionHandler 注解进行局部异常处理。使用 ExceptionHandler 注解&am…

哈工大计算网络课程数据链路层详解之:数据链路层服务

哈工大计算网络课程数据链路层详解之&#xff1a;数据链路层服务 在介绍完网络层的实现功能和协议之后&#xff0c;接下来我们继续介绍网络层的下一层&#xff1a;数据链路层。 本节首先对数据链路层的功能和所提供的服务进行概述。 如下图示例网络所示&#xff0c;标红色的部…