【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.hqwc.cn/news/192662.html

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

相关文章

Spring Boot - filter 的顺序

定义过滤器的执行顺序 1、第一个过滤器 import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; impor…

LeetCode【4】寻找两个正序数组中位数

题目&#xff1a; 思路&#xff1a; https://blog.csdn.net/a1111116/article/details/115033098 代码&#xff1a; public double findMedianSortedArrays(int[] nums1, int[] nums2) {int[] ints Arrays.copyOf(nums1, nums1.length nums2.length);System.arraycopy(nums2…

gRPC 四模式之 双向流RPC模式

双向流RPC模式 在双向流 RPC 模式中&#xff0c;客户端以消息流的形式发送请求到服务器端&#xff0c;服务器端也以消息流的形式进行响应。调用必须由客户端发起&#xff0c;但在此之后&#xff0c;通信完全基于 gRPC 客户端和服务器端的应用程序逻辑。 为什么有了双向流模式…

【SQL server】数据库、数据表的创建

创建数据库 --如果存在就删除 --所有的数据库都存在sys.databases当中 if exists(select * from sys.databases where name DBTEST)drop database DBTEST--创建数据库 else create database DBTEST on --数据文件 (nameDBTEST,--逻辑名称 字符串用单引号filenameD:\DATA\DBT…

操作系统(五)| 文件系统上 结构 存取方式 文件目录 检索

文章目录 1 文件系统概述2 文件的结构与存取方式2.1 磁盘2.2 文件的物理结构2.2.1 连续结构2.2.2 链式结构2.2.3 索引结构 2.3 文件的存取方式 3 文件目录3.1 基本概念3.2 目录结构单级目录结构多级目录结构 3.3 文件目录检索3.3.1 目录检索文件寻址 3.4 文件目录的实现 1 文件…

08-黑马点评项目发布笔记和查看笔记功能的实现

发布笔记 数据模型 tb_blog探店笔记表,包含笔记的标题、文字、图片等 tb_blog探店笔记表对应的实体类 增加用户图标和和用户姓名以及是否被点赞过了的字段,这些字段不属于Blog表只是为了实现在展示笔记的时候同时展示用户的信息 Data EqualsAndHashCode(callSuper false) …

Linux发展历程

<!DOCTYPE html> <html> <head> <meta charset"UTF-8"> <title>Linux历史发展</title> <style> /* CSS样式 */ body { font-family: Arial, sans-serif; margin: 0;…

动手学深度学习——循环神经网络的从零开始实现(原理解释+代码详解)

文章目录 循环神经网络的从零开始实现1. 独热编码2. 初始化模型参数3. 循环神经网络模型4. 预测5. 梯度裁剪6. 训练 循环神经网络的从零开始实现 从头开始基于循环神经网络实现字符级语言模型。 # 读取数据集 %matplotlib inline import math import torchfrom torch import …

某60区块链安全之重入漏洞实战记录

区块链安全 文章目录 区块链安全重入漏洞实战实验目的实验环境实验工具实验原理实验内容 重入漏洞实战 实验目的 学会使用python3的web3模块 学会以太坊重入漏洞分析及利用 实验环境 Ubuntu18.04操作机 实验工具 python3 实验原理 以太坊智能合约的特点之一是能够调用和…

进程概述

文章目录 计算机算机组成因特尔CPU型号摩尔定律衡量CPU的指标指令&#xff08;Instruction)操作系统&#xff08;Operating System&#xff09;虚拟地址空间&#xff08;Virtual Address Space&#xff09;进程(Process/task)进程管理(PCB - 进程控制块)进程控制块&#xff08;…

2023亚太杯数学建模思路 - 案例:异常检测

文章目录 赛题思路一、简介 -- 关于异常检测异常检测监督学习 二、异常检测算法2. 箱线图分析3. 基于距离/密度4. 基于划分思想 建模资料 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 一、简介 – 关于异常…

UE 调整材质UV贴图长宽比例

首先&#xff0c;为什么要先减去0.5呢&#xff0c;因为缩放的贴图中心在0,0原点&#xff0c;以这个点缩放效果是这样&#xff1a; 它缩放的图案不会在正中间&#xff0c;因为是以0,0点进行缩放的 以这个图的箭头去缩放图片的&#xff0c;所以不能使得缩放后的图片放在正中心 那…

开源WIFI继电器之方案介绍

一、实物 1、外观 2、电路板 二、功能说明 输出一路继电器常开信号&#xff0c;最大负载电流10A输入一路开关量检测联网方式2.4G Wi-Fi通信协议MQTT配网方式AIrkiss&#xff0c;SmartConfig设备管理本地Web后台管理&#xff0c;可配置MQTT参数供电AC220V其它一个功能按键&…

java并发编程之基础与原理2

cpu缓存结构剖析 下面说一下概念与作用 CPU缓存即高速缓冲存储器&#xff0c;是位于CPU与主内存间的一种容量较小但速度很高的存储 器。由于CPU的速度远高于主内存&#xff0c;CPU直接从内存中存取数据要等待一定时间周期&#xff0c;Cache中 保存着CPU刚用过或循环使用的一部…

二维码智慧门牌管理系统升级解决方案:高效运营,信息尽在掌握

文章目录 前言一、升级要点二、方案优势三、应用场景四、客户案例 前言 在这个日新月异的时代&#xff0c;二维码智慧门牌管理系统已经成为了各行各业的标配。为了更好地满足用户需求&#xff0c;提升运营效率&#xff0c;我们推出了全新的升级解决方案。这个方案将让你轻松掌…

【DevOps】Git 图文详解(四):Git 使用入门

Git 图文详解&#xff08;四&#xff09;&#xff1a;Git 使用入门 1.创建仓库2.暂存区 add3.提交 commit 记录4.Git 的 “指针” 引用5.提交的唯一标识 id&#xff0c;HEAD~n 是什么意思&#xff1f;6.比较 diff 1.创建仓库 创建本地仓库的方法有两种&#xff1a; 一种是创建…

nodejs微信小程序-慢性胃炎健康管理系统的设计与实现-安卓-python-PHP-计算机毕业设计

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…

爱上C语言:操作符详解(下)

&#x1f680; 作者&#xff1a;阿辉不一般 &#x1f680; 你说呢&#xff1a;生活本来沉闷&#xff0c;但跑起来就有风 &#x1f680; 专栏&#xff1a;爱上C语言 &#x1f680;作图工具&#xff1a;draw.io(免费开源的作图网站) 如果觉得文章对你有帮助的话&#xff0c;还请…

【漏洞复现】泛微e-Weaver SQL注入

漏洞描述 泛微e-Weaver&#xff08;FANWEI e-Weaver&#xff09;是一款广泛应用于企业数字化转型领域的集成协同管理平台。作为中国知名的企业级软件解决方案提供商&#xff0c;泛微软件&#xff08;广州&#xff09;股份有限公司开发和推广了e-Weaver平台。 泛微e-Weaver旨在…