【C++入门到精通】右值引用 | 完美转发 C++11 [ C++入门 ]

在这里插入图片描述

阅读导航

  • 引言
  • 一、左值引用和右值引用
    • 1. 什么是左值?什么是左值引用?
    • 2. 什么是右值?什么是右值引用?
    • 3. move( )函数
  • 二、左值引用与右值引用比较
  • 三、右值引用使用场景和意义
  • 四、完美转发
    • std::forward 函数
    • 完美转发实际中的使用场景
  • 温馨提示

引言

当谈到C++的高级特性时,右值引用是一个不可忽视的重要概念。作为一种在C++11标准中引入的语言特性,右值引用为我们提供了更加灵活和高效的内存管理方式。它不仅可以优化代码性能,还可以改善对象拷贝行为,使得我们能够更好地处理临时对象和移动语义。通过深入理解右值引用的原理和使用方法,我们可以在C++编程中发挥出更大的威力,提升代码的效率和可维护性。本文将全面介绍右值引用的概念、用法和相关的重要概念,帮助读者更好地理解和应用这一关键特性。无论您是初学者还是有经验的程序员,都将从本文中获得对右值引用的深入认识,并能够在实际项目中灵活运用。让我们一起探索C++中右值引用的奇妙世界吧!🥰

一、左值引用和右值引用

传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,总的来说就是无论左值引用还是右值引用,都是给对象取别名。

1. 什么是左值?什么是左值引用?

在C++中,左值是指表达式结束后依然存在的数据对象,它可以出现在赋值操作的左边或右边。通常来说,变量、函数返回的引用、解引用操作等都是左值。简言之,左值可以被赋值,可以取地址。

左值引用是指对左值进行引用的方式。它使用&符号声明,可以绑定到一个左值上,从而允许我们通过引用修改原始的左值对象。左值引用就是给左值的引用,给左值取别名。左值引用在函数参数传递和函数返回值中经常被使用,能够避免不必要的复制,并且可以实现对原始对象的直接操作。左值引用也为后续引入右值引用打下了基础,是C++语言中非常重要的概念之一。

int main()
{// 以下的p、b、c、*p都是左值int* p = new int(0);int b = 1;const int c = 2;// 以下几个是对上面左值的左值引用int*& rp = p;int& rb = b;const int& rc = c;int& pvalue = *p;return 0;
}

2. 什么是右值?什么是右值引用?

在C++中,右值是指表达式结束后即将被销毁的临时数据对象,它通常不能出现在赋值操作的左边。字面上来说,右值就是“赋值运算符=右边的值”。比如,常量、临时对象、表达式的计算结果等都可以是右值。

右值引用是C++11引入的新特性,使用双&&符号声明,它可以绑定到一个右值或将要销毁的对象上。右值引用的引入使得我们能够实现移动语义,即将资源(如内存)的所有权从一个对象转移到另一个对象,而不需要进行深层的复制操作,从而提高了代码的效率和性能。右值引用还为移动构造函数和移动赋值运算符的实现提供了基础,这些特性在处理大型数据结构时非常有用。右值引用的引入使得C++语言能够更好地支持移动语义,从而更好地适应现代编程的需求。

int main()
{double x = 1.1, y = 2.2;// 以下几个都是常见的右值10;x + y;fmin(x, y);// 以下几个都是对右值的右值引用int&& rr1 = 10;double&& rr2 = x + y;double&& rr3 = fmin(x, y);// 这里编译会报错:error C2106: “=”: 左操作数必须为左值10 = 1;x + y = 1;fmin(x, y) = 1;return 0;
}

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

int main()
{double x = 1.1, y = 2.2;int&& rr1 = 10;const double&& rr2 = x + y;rr1 = 20;rr2 = 5.5; // 报错return 0;
}

3. move( )函数

std::move() 是C++11引入的一个函数模板,位于头文件 <utility> 中。它用于将传入的对象转换为右值引用,从而支持移动语义。在移动语义中,对象的资源所有权可以从一个对象转移到另一个对象,而不需要进行深层的复制操作,这可以提高程序的性能和效率。

std::move() 的定义如下:

template <class T>
constexpr remove_reference_t<T>&& move(T&& t) noexcept;

其中,t 是一个通用引用,它可以绑定到左值或右值。std::move()t 转换为右值引用并返回,即使 t 是一个左值,也可以通过 std::move() 转为右值引用。

使用 std::move() 主要用于以下两个场景:

  1. 在实现移动构造函数和移动赋值运算符时,可以使用 std::move() 将成员变量转换为右值引用,从而实现资源的转移而非复制。
  2. 在标准库中,例如容器的 insertemplace 方法中,使用 std::move() 可以将对象的所有权转移到容器中,避免不必要的复制操作。

需要注意的是,std::move() 仅仅是将对象转换为右值引用,它本身并不会进行实际的资源移动操作。因此,在使用 std::move() 后,程序员仍需谨慎处理对象的生命周期,以避免悬挂指针或对象被多次释放等问题。

二、左值引用与右值引用比较

⭕左值引用总结:

  1. 左值引用只能引用左值,不能引用右值。
  2. 但是const左值引用既可引用左值,也可引用右值
int main()
{// 左值引用只能引用左值,不能引用右值。int a = 10;int& ra1 = a; // ra为a的别名//int& ra2 = 10; // 编译失败,因为10是右值// const左值引用既可引用左值,也可引用右值。const int& ra3 = 10;const int& ra4 = a;return 0;
}

⭕右值引用总结:

  1. 右值引用只能右值,不能引用左值。
  2. 但是右值引用可以move以后的左值.
int main()
{// 右值引用只能右值,不能引用左值。int&& r1 = 10;// error C2440: “初始化”: 无法从“int”转换为“int &&”// message : 无法将左值绑定到右值引用int a = 10;int&& r2 = a;// 右值引用可以引用move以后的左值int&& r3 = std::move(a);return 0;
}

三、右值引用使用场景和意义

  1. 移动语义:右值引用的最重要的使用场景之一就是实现移动语义。通过移动语义,可以避免不必要的深层复制操作,提高程序的性能和效率。移动语义通常在以下情况下使用:
    • 移动构造函数和移动赋值运算符:通过将资源的所有权从一个对象转移到另一个对象,而非进行深层的复制操作,来提高效率。
    • 标准库中的容器和算法:许多标准库中的容器和算法都利用了移动语义,例如移动构造和移动赋值来提高性能。

例如在下面这段代码中,使用了右值引用来实现移动语义,从而避免不必要的深层复制操作,提高了对象的构造和赋值效率。

移动构造函数的定义如下:

string::string(string&& s): _str(nullptr), _size(0), _capacity(0)
{cout << "string(string&& s) -- 移动语义" << endl;swap(s);
}

在移动构造函数中,接收一个右值引用作为参数,通过 && 标识符表示。在函数体内部,输出一条信息以表明这是移动构造函数,并且调用了 swap() 函数来交换资源,实现了移动语义。

移动赋值运算符的定义如下:

string& string::operator=(string&& s)
{cout << "string& operator=(string&& s) -- 移动语义" << endl;swap(s);return *this;
}

在移动赋值运算符中,同样接收一个右值引用作为参数。在函数体内部,输出一条信息以表明这是移动赋值运算符,并且调用了 swap() 函数来交换资源,实现了移动语义。

  1. 完美转发:右值引用与通用引用(universal reference)结合使用时,可以实现完美转发。完美转发允许将函数参数按原样传递给其他函数,无论原始参数是左值还是右值。这对于泛型编程以及实现转发函数(forwarding function)非常有用。

  2. 优化临时对象:临时对象是在表达式求值过程中创建的临时值,它们的生命周期很短暂,并且通常在表达式结束后立即销毁。通过使用右值引用,可以避免不必要的拷贝构造和析构操作,提高代码的性能和效率。

  3. 移动语义和资源管理:右值引用在资源管理方面非常有用,例如管理动态分配的内存、文件句柄、网络连接等。通过使用右值引用,可以实现资源的移动而非复制,从而提高程序的性能和可维护性。

  4. 避免不必要的拷贝构造和析构:当需要返回临时对象时,通过使用右值引用可以避免不必要的拷贝构造和析构,提高代码的效率。

四、完美转发

模板中的&& 万能引用

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; }

模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发

std::forward 函数

std::forward 是C++标准库中的一个函数模板,位于 <utility> 头文件中。它用于实现完美转发,将传入的参数以原样转发给其他函数。

std::forward 的函数模板定义如下:

template <typename T>
T&& forward(typename std::remove_reference<T>::type& arg) noexcept;template <typename T>
T&& forward(typename std::remove_reference<T>::type&& arg) noexcept;

这个函数模板有两个重载版本,接受一个通用引用作为参数。它使用了 typename std::remove_reference<T>::type 来移除参数的引用限定符,以保持参数的值类别(左值或右值)。

当传入一个左值时,std::forward 返回一个左值引用;当传入一个右值时,std::forward 返回一个右值引用。这样就可以保持参数在转发过程中的值类别不变。

std::forward 的主要应用场景是在模板函数中进行完美转发,将参数原样传递给其他函数。通过使用 std::forward,可以避免不必要的拷贝和移动操作,提高代码的性能和效率。

以下是使用 std::forward 进行完美转发的示例:

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; }// std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
template<typename T>void PerfectForward(T&& t)
{Fun(std::forward<T>(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;
}

完美转发实际中的使用场景

template<class T>
struct ListNode
{ListNode* _next = nullptr;ListNode* _prev = nullptr;T _data;
};template<class T>
class List
{typedef ListNode<T> Node;public:List(){// 创建一个头节点,并将头节点的_next和_prev都指向自身,表示链表为空_head = new Node;_head->_next = _head;_head->_prev = _head;}void PushBack(T&& x){// 在链表尾部插入一个右值Insert(_head, std::forward<T>(x));}void PushFront(T&& x){// 在链表头部插入一个右值Insert(_head->_next, std::forward<T>(x));}void Insert(Node* pos, T&& x){// 在指定位置之前插入一个右值// 获取pos节点的前一个节点Node* prev = pos->_prev;// 创建一个新的节点Node* newnode = new Node;// 使用完美转发将右值x赋值给新节点的_datanewnode->_data = std::forward<T>(x);// 调整链表中的指针prev->_next = newnode;newnode->_prev = prev;newnode->_next = pos;pos->_prev = newnode;}void Insert(Node* pos, const T& x){// 在指定位置之前插入一个左值// 获取pos节点的前一个节点Node* prev = pos->_prev;// 创建一个新的节点Node* newnode = new Node;// 将左值x赋值给新节点的_datanewnode->_data = x;// 调整链表中的指针prev->_next = newnode;newnode->_prev = prev;newnode->_next = pos;pos->_prev = newnode;}private:Node* _head;
};int main()
{List<bit::string> lt;lt.PushBack("1111");lt.PushFront("2222");return 0;
}

上面这段代码是一个简化的链表实现,包括了节点结构 ListNode 和链表类 List。其中,链表类中的 PushBackPushFrontInsert 函数用于在链表中插入元素。

Insert 函数中,有两个重载版本,分别用于插入右值引用和左值引用。关键位置是对节点的 _data 成员赋值的地方。

对于右值引用版本,使用 std::forward<T>(x) 将参数 x 原样转发,保持其原始值类别。这样做可以避免不必要的拷贝操作,提高性能和效率。

对于左值引用版本,直接将参数 x 赋值给节点的 _data 成员。因为左值引用已经是一个具名对象,没有必要进行移动或拷贝操作。

在主函数中,创建了一个 List<bit::string> 类型的链表对象 lt,并通过 PushBackPushFront 函数向链表中插入元素。

总的来说,这段代码展示了如何使用完美转发和模板来实现一个简单的链表,并在插入元素时考虑了右值引用和左值引用的情况,以提高代码的灵活性和效率。

温馨提示

感谢您对博主文章的关注与支持!另外,我计划在未来的更新中持续探讨与本文相关的内容,会为您带来更多关于C++以及编程技术问题的深入解析、应用案例和趣味玩法等。请继续关注博主的更新,不要错过任何精彩内容!

再次感谢您的支持和关注。期待与您建立更紧密的互动,共同探索C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!
在这里插入图片描述

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

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

相关文章

【LearnOpenGL基础入门——3】绘制纯色三角形

目录 一.写在前面 二.顶点输入 三.顶点着色器 四.编译着色器 五.片段着色器 六.着色器程序 七.链接顶点属性 彩蛋 一.写在前面 我们先认识一下OpenGL常用的几个名词&#xff1a; 顶点数组对象&#xff1a;Vertex Array Object&#xff0c;VAO顶点缓冲对象&#xff1a;…

2023全新付费进群系统源码 带定位完整版 附教程

这源码是我付费花钱买的分享给大家&#xff0c;功能完整。 搭建教程 Nginx1.2 PHP5.6-7.2均可 最好是7.2 第一步上传文件程序到网站根目录解压 第二步导入数据库&#xff08;58soho.cn.sql&#xff09; 第三步修改/config/database.php里面的数据库地址 第四步修改/conf…

linux配置固定ip(两种方法)

首先刚下载的vm&#xff0c;刚创建的虚拟机&#xff0c;肯定是需要配置ip的 其次以前我的每次都是设置自动ip&#xff0c;这样每次登录都会自动获取ip地址&#xff0c;并且每次的ip都不相同。 ~方法&#xff1a; 开机登陆后 1)Cd /etc/sysconfig/network-scripts 2)Vi ifcf…

Elasticsearch备份与还原:使用elasticdump

在数据管理的世界里&#xff0c;备份和还原数据是重中之重的日常工作&#xff0c;特别是对于Elasticsearch这样的强大而复杂的搜索引擎。备份不仅可以用于灾难恢复&#xff0c;还可以在数据迁移、测试或者升级等场景中发挥重要作用。 在本博客中&#xff0c;我们将会重点介绍如…

轻量级 Java 日志组件

日志记录功能在开发中很常用&#xff0c;不仅可以记录程序运行的细节&#xff0c;方便调试&#xff0c;也可以记录用户的行为&#xff0c;是框架中不可或缺的组件。为最大程度复用现有的组件&#xff0c;我们就地取材使用了 JDK 自带的 JUL&#xff08;java.util.logging&#…

聚观早报 |联想集团Q2财季业绩;小鹏汽车Q3营收

【聚观365】11月17日消息 联想集团Q2财季业绩 小鹏汽车Q3营收 微软发布两款自研AI芯片 FAA批准SpaceX再次发射星际飞船 2023 OPPO开发者大会 联想集团Q2财季业绩 全球数字经济领导企业联想集团公布截至2023年9月30日的2023/24财年第二财季业绩&#xff1a;整体营收达到10…

微信小程序H5 uniapp

最近微信小程序对有视频播放的审核严&#xff0c;需要提供“文娱类资质”。而申请这个资质比较繁琐。所以我们在小程序上用web-view做跳转到H5&#xff0c;H5使用uniapp编写。这是小程序关于web-view文档说明。https://developers.weixin.qq.com/miniprogram/dev/component/web…

硬件工程师基础能力课

第一课时--基本定理、电阻、电容等 首先了解下面几个概念&#xff0c;基尔霍夫定理&#xff1a;KCL & KVL&#xff0c;叠加定理&#xff0c;戴维南定理&#xff08;电压源等效&#xff09;和诺顿定理&#xff08;电流源等效&#xff09;、奈奎斯特采样定理。 上面说的这些东…

asp.net core EF Sqlserver

一、EF CORE的使用 1、使用NuGet来安装EF CORE 使用程序包管理器控制台&#xff0c;进行命令安装 //安装 Microsoft.EntityFrameworkCoreInstall-Package Microsoft.EntityFrameworkCore //安装 Microsoft.EntityFrameworkCore.SqlServer Install-Package Microsoft.EntityF…

产品运营的场景和运营策略

一、启动屏 1&#xff0e;概念 启动屏&#xff0c;特指 APP 产品启动时即显示的界面&#xff0c;这个界面一般会停留几秒钟时间&#xff0c;在这个时间内 APP 会在后台加载服务框架、启动各种服务 SDK 、获取用户地理位置、判断有无新版本、判断用户账户状态以及其他系统级别的…

微机原理_12

一、单项选择题(本大题共15小题,每小题3分&#xff0c;共45分。在每小题给出的四个备选项中&#xff0c;选出一个正确的答案。〕 十进制正数56的 8位二进制补码是()。 A. 00011001 B. 10100110 C. 10011001 D. 00100110 若栈顶的物理地址为20100H&#xff0c;当执行完指令PUSH…

qt 重载信号,使用““方式进行connect()调用解决方案

问题 在Qt中&#xff0c;重载的信号默认是无法使用&这种方式调用的。 因为&只能绑定到一个具体的信号&#xff0c;而重载的信号名称相同&#xff0c;编译器无法确定要绑定哪一个信号。 解决方案 如果非要使用&绑定重载的信号&#xff0c;可以使用函数指针进行转…

2023年道路运输企业主要负责人证考试题库及道路运输企业主要负责人试题解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年道路运输企业主要负责人证考试题库及道路运输企业主要负责人试题解析是安全生产模拟考试一点通结合&#xff08;安监局&#xff09;特种作业人员操作证考试大纲和&#xff08;质检局&#xff09;特种设备作业人…

基于SpringBoot的SSMP整合案例(消息一致性处理与表现层开发)

消息一致性处理 在后端执行完相应的操作后&#xff0c;我们需要将执行操作后的结果与数据返回前端&#xff0c;前端 调用我们传回去的数据&#xff0c;前端是如何知道我们传回去的数据名称的&#xff1f; 答&#xff1a;前后端遵循了同一个"协议"。这个协议就是定义…

【STM32】CRC(循环冗余校验)

一、CRC的背景知识 1、什么是CRC (1)CRC&#xff08;Cyclic Redundancy Check&#xff09;&#xff0c;循环冗余校验 (2)什么是校验&#xff0c;为什么需要校验&#xff1a;数据传输&#xff0c;数据存储过程中需要使用到的 (3)什么是冗余&#xff1a;表示比实际上要传输的数据…

docker 安装mongodb 实现 数据,日志,配置文件外挂

docker 安装mongodb 实现数据&#xff0c;日志&#xff0c;配置文件外挂 1 背景 最近开发了一个评论系统之前用mysql来存储数据&#xff0c;但是考虑到后期业务增大访问量也会增大&#xff0c;为了兼容这种高并发的场景&#xff0c;因此经过多方面的考虑&#xff0c;我们最终…

2023年咸阳市《网络建设与运维》赛题解析------四、安全配置

安全配置 说明:IP地址按照题目给定的顺序用“ip/mask”表示,IPv4 any地址用0.0.0.0/0,IPv6 any地址用::/0,禁止用地址条目,否则按零分处理。 1.FW1配置IPv4 nat,实现集团产品1段IPv4访问Internet IPv4,转换ip/mask为200.200.200.16/28,保证每一个源IP产生的所有会话将…

Java拼图游戏

运行出的游戏界面如下&#xff1a; 按住A不松开&#xff0c;显示完整图片&#xff1b;松开A显示随机打乱的图片。 User类 package domain;/*** ClassName: User* Author: Kox* Data: 2023/2/2* Sketch:*/ public class User {private String username;private String password…

超详细 | 萤火虫算法原理及其实现(Matlab)

群智能(Swarm Intelligence&#xff0c;SI)是一类分散自组织系统的集体智能行为的总称&#xff0c;该表述最早在1989年由Gerardo Beni在分子自动机系统中提出。SI系统可视作一组简单的个体&#xff0c;其个体与个体、个体与环境之间存在交互作用&#xff0c;最终表征出智能行为…

MPN – 制造零件号

S/4 1610 中的 MPN – 基于 NAST 的输出管理 我试图查找有关 MPN 设置的信息&#xff0c;但找不到详细的配置步骤。在浏览了一些信息和 help.sap 链接后&#xff0c;我能够在 S/4 1610 系统中配置 MPN 设置&#xff0c;这与使用旧输出类型&#xff08;Nast 和输出类型 NEU&…