引用折叠、万能引用、右值引用、move、完美转发

大家好,我叫徐锦桐,个人博客地址为www.xujintong.com。平时记录一下学习计算机过程中获取的知识,还有日常折腾的经验,欢迎大家来访。

在写STL源码的时候遇到的问题,在这里写一篇笔记记录一下。

一、引用折叠

引用折叠表示了一个值被引用多次时(只有在模板推导时候),会生成什么类型。

T& & 折叠成 T&
T& && 折叠成 T&
T&& & 折叠成 T&
T&& && 折叠成 T&&

其实总结起来就是,只有两次都是右值引用的时候才是T&&,即T&& && 折叠成 T&&,其他都是转换成左值引用T&。

二、左值引用

左值引用,就是个别名,将一个变量绑定到另一个变量上。

int a = 10;
int &b = a;     // 左值引用
const int &c = 10;  // 常量左值引用b = 20;
std::cout << a << std::endl;  // 20

修改引用变量会影响原来的变量的值,但是 const int & 不能修改原对象的值(中间的int可以更换为其他类型)。
一般来说,函数传参的时候是拷贝传参,也就是将变量复制到一个临时变量,再将临时变量传入函数,最后销毁临时变量。这里涉及到了内存的拷贝,消耗的时间比较长。但是如果在传参的时候使用左值引用,不会涉及到内存的拷贝,因为是别名,相当于直接修改原变量了。

void left_value(int& x) {x = 200;
}int main()
{  int value = 10;left_value(value);std::cout << value << std::endl;  // 200return 0;
}



当然如果是const&的话,虽然不涉及到内存的拷贝过程,但是不能修改原变量。

void left_value(const int& x) {x = 200;     // error: assignment of read-only reference 'x'
}int main()
{  int value = 10;left_value(value);std::cout << value << std::endl;  // 200return 0;
}

这里函数传参的时候,为什么 int 能匹配到 int& 或者 const int& 呢?

当你将一个int类型传递给int&参数时,c++会执行一个类型转换,将int隐式的转换为int&,另一个同理。注意:int、int&、const int& 在匹配函数的时候优先级一样,同时出现会发生歧义错误。
函数传参的时候隐式转换很常见。

void left_value(int num) {std::cout << num << std::endl;
}int main()
{  float a = 1.5;left_value(a);    // 1return 0;
}

传入的时候隐式的将float类型转化为int类型。


但是用函数重载就可以解决这个问题。

void left_value(int num) {std::cout << num << std::endl;
}void left_value(float num) {std::cout << num << std::endl;
}int main()
{  float a = 1.5;left_value(a);return 0;
}

会调用最适合的函数,那如果没有的话就只能进行隐式的类型转换了。

三、右值引用

右值引用是c++11引用的新特性。就是左值引用是给一个变量加别名,而右值引用就是绑定变量到一个临时值上,临时变量的生命周期和新左值的生命周期绑定,原来的临时变量销毁。

int &&d = 10;   // 右值引d = 200;  // 此时 d 是变量是左值

引用在c++中是一个特别的类型,因为它的值类型和变量类型不一样, 左值/右值引用变量的值类型都是左值, 而不是左值引用或者右值引用。 这句话意思非常重要,简单来说,看上面代码,一开始将d绑定到了10上面,然后又将d赋值,在int &&d=10 之后,每次使用d,d都是作为一个左值使用的。一个右值引用变量在使用上就变成了左值,已经不再携带其是右引用这样的信息,只是一个左值。


右值引用也是提高性能用的。

vector<string> v;
string s = "teststring";
v.push_back(s);

上面这个代码,用了一个临时变量。假如我们啥都不干那么是怎么进行的呢,首先会先创建一个临时字符串,然后将这个临时字符串拷贝到v的内存上,然后再销毁这个临时字符串。中间进行内存的开辟和销毁,在一般数据上性能没啥问题,如果是数据特别多的话,性能就会严重下降。


但如果我们用右值引用,如下代码。

vector<string> v;
string s = "teststring";
v.push_back(std::move(s));

这里的std::move()是将一个左值强制转换为右值,下面我会讲,现在就知道能将左值强制转化为右值就行了。
中间的过程就变成了,创建一个临时字符串,然后直接将这个临时字符串挂载到v上面。是不是中间少了好多过程,性能也大大的提高了。

四、万能引用

模板编程中,有的时候需要传入左值也传入右值引用。
下面这个只能传入一个左值,如果传入右值就会报错。

template <typename T>
void func(T& value) {std::cout << "函数调用" << std::endl;
}int main()
{int value = 10;func(value);func(10); // 报错return 0;
}



这时候就用到了我们的万能引用T&&

template <typename T>
void func(T&& value) {std::cout << "函数调用" << std::endl;
}int main()
{int value = 10;func(value);func(10); return 0;
}

解释一下:
能同时传入左值引用和右值引用。
如果是左值,T先会推导成T&,然后再发生引用折叠,如果是右值引用,T会推导成T&&。
比如说,func(value)中的value是一个左值,传入后T会推导成T&,然后再后后面的&&发生折叠引用,就会变成T&。
func(10)中的10是一个右值,T会推导成T&&,然后再和后边的&&发生引用折叠,最后变成了T&&。
**T&&只在

五、move

std::move它其实啥都没干,就是强制将左值转化为右值。
show you the code

template<typename _Tp>constexpr typename std::remove_reference<_Tp>::type&&move(_Tp&& __t) noexcept{ return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

这里面的 std::remove_reference<_Tp>,就像去除变量的引用,假如_Tp是int&&,最后返回type是个int。


std::remove_reference<_Tp>的源代码如下。

/// remove_referencetemplate<typename _Tp>struct remove_reference{ typedef _Tp   type; };template<typename _Tp>struct remove_reference<_Tp&>{ typedef _Tp   type; };template<typename _Tp>struct remove_reference<_Tp&&>{ typedef _Tp   type; };template<typename _Tp, bool = __is_referenceable<_Tp>::value>struct __add_lvalue_reference_helper{ typedef _Tp   type; };template<typename _Tp>struct __add_lvalue_reference_helper<_Tp, true>{ typedef _Tp&   type; };

六、完美转发

完美转发是配合万能引用使用的,通过万能引用传入左值和右值的参数,然后通过完美转发保留这个属性,转发给对应的重载函数。
show the code

/*函数模板的重载
*/
template <typename T>
void to_forward(T& value) {std::cout << "调用的左值函数" << std::endl;
}template <typename T>
void to_forward(T&& value) {std::cout << "调用的右值函数" << std::endl;
}template <typename T>
void func(T&& arg) {to_forward(arg);  // 调用左边值函数
}int main(){int value = 10;func(std::move(value));
}

在这个代码中,func函数中的to_forward函数最终会调用to_forward(T& value),虽然传入的是右值,一个右值引用变量在使用上就变成了左值,已经不再携带其是右引用这样的信息,只是一个左值,所以这里调用的是左值的重载函数。


首先展示一下std::forward的源码。

/***  @brief  Forward an lvalue.*  @return The parameter cast to the specified type.**  This function is used to implement "perfect forwarding".*/template<typename _Tp>constexpr _Tp&&forward(typename std::remove_reference<_Tp>::type& __t) noexcept{ return static_cast<_Tp&&>(__t); }/***  @brief  Forward an rvalue.*  @return The parameter cast to the specified type.**  This function is used to implement "perfect forwarding".*/template<typename _Tp>constexpr _Tp&&forward(typename std::remove_reference<_Tp>::type&& __t) noexcept{static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"" substituting _Tp is an lvalue reference type");return static_cast<_Tp&&>(__t);}

这里有两个重载的函数,一个对应左值一个对应右值。
forward(typename std::remove_reference<_Tp>::type& __t)这个是先将_t的类型去掉引用,然后加上个&,也就是最后是左值类型。
那下面forward(typename std::remove_reference<_Tp>::type&& __t)那个肯定就是右值的了。


这里也用到了引用折叠,连个返回的方法是一样的,都是return static_cast<_Tp&&>(__t),如果_Tp是&,然后在和后面的&&折叠就是左值了;如果_Tp是&&,然后再和后面的&&折叠还是&&。


std::move和std::forward其实什么都没做,只是强制转换了一下类型。


看下面这个代码,这个是正确的。我们通过完美转发保留了这个左值还是右值的属性,然后再传给另一个函数。

#include <iostream>
#include <utility>/* 完美转发 + 万能引用 + std::move *//*函数模板的重载
*/
template <typename T>
void to_forward(T& value) {std::cout << "调用的左值函数" << std::endl;
}template <typename T>
void to_forward(T&& value) {std::cout << "调用的右值函数" << std::endl;
}/*万能引用:能同时传入左值引用和右值引用如果是左值,T先会推导成T&,然后再发生引用折叠入股是右值引用,则会什么都不干利用完美转发std::forward:先通过万能引用可以传入左值引用和右值引用然后通过完美转发(能保留传入时候是左值引用还是右值引用的属性,然后转发到对应的函数重载中
*/
template <typename T>
void func(T&& arg) {// 保留左值和右值的属性to_forward(std::forward<T>(arg));
}int main() {int value = 10;int& value_l_refernce = value;func(value);       // 调用的左值引用函数func(value_l_refernce);       // 调用的左值引用函数func(100);         // 调用的右值引用函数int&& value_r_refernce = 30;// 右值引用使用后,之后调用这个变量都是作为左值func(value_r_refernce);     // **调用的左值引用**func(std::move(value));     // 调用的右值引用return 0;
}

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

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

相关文章

分享5个解决msvcp140.dll丢失的方法,全面解析msvcp140.dll丢失的原因

一、MSVCP140.dll是什么&#xff1f; 首先&#xff0c;我们需要了解什么是MSVCP140.dll。MSVCP140.dll是一个动态链接库文件&#xff0c;它是Microsoft Visual C 2015 Redistributable的一部分。这个文件包含了运行使用C编写的应用程序所需的一些函数和类。因此&#xff0c;当…

【单元测试】--测试驱动开发(TDD)

一、什么是测试驱动开发 测试驱动开发&#xff08;Test-Driven Development&#xff0c;TDD&#xff09;是一种软件开发方法&#xff0c;其核心思想是在编写实际代码之前&#xff0c;首先编写测试用例。TDD 的主要步骤如下&#xff1a; 编写测试用例&#xff1a;首先&#xf…

MongoDB URL链接 如何设置账号密码

个人博客&#xff0c;求关注。。 MongoDB URL链接 如何设置账号密码 假设你的账号是root&#xff0c;你的密码也是root&#xff0c;则 mongodb://username:passwordlocalhost:27017完美&#xff0c;再见。

探索LLM在图上学习的潜力10.14 暂停

探索LLM在图上学习的潜力 摘要介绍初步知识 摘要 Learning on Graph已经引起了极大的关注&#xff0c;因为它在现实世界中有广泛的应用。在具有文本节点属性的图形上进行学习的最流行的流程主要依赖于图神经网络&#xff08;GNN&#xff09;&#xff0c;并利用浅层文本嵌入作为…

Maven系列第8篇:大型Maven项目,快速按需任意构建

本篇涉及到的内容属于神技能&#xff0c;多数使用maven的人都经常想要的一种功能&#xff0c;但是大多数人都不知道如何使用&#xff0c;废话不多说&#xff0c;上干货。 需求背景 我们需要做一个电商项目&#xff0c;一般都会做成微服务的形式&#xff0c;按业务进行划分&am…

【LeetCode 算法专题突破】滑动窗口(⭐)

文章目录 前言1. 长度最小的子数组题目描述代码 2. 无重复字符的最长子串题目描述代码 3. 最大连续1的个数 III题目描述代码 4. 将 x 减到 0 的最小操作数题目描述代码 5. 水果成篮题目描述代码 6. 找到字符串中所有字母异位词题目描述代码 7. 串联所有单词的子串题目描述代码 …

关于我对 jeecg-boot 的项目理解、使用心得和改进建议

一句话总结&#xff1a; JeecgBoot帮助我提升了后端技术水平&#xff0c;入门了前端&#xff0c;让我在公司内部慢慢能够成长为全栈开发。 一、项目理解 JeecgBoot 项目的核心理念是快速开发、低代码、易扩展。它采用了前后端分离的架构&#xff0c;后端使用Spring Boot Myba…

用节点亲和性把 Pod 分配到节点

用节点亲和性把 Pod 分配到节点 当前集群信息&#xff1a; rootk8s-master:~# kubectl get node -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME k8s…

cuda PyTorch

1. GPU对应的CUDA版本 nvidia-smi CUDA Version: 12.2 GPU diver 大于cuda toolkit&#xff0c; pytorch 版本根据cuda toolkit 2. 查看nvcc的版本&#xff08;即cuda toolkit 版本&#xff09; nvcc --version Cuda compilation tools, release 12.2, V12.2.91 Build cud…

微信小程序OA会议系统数据交互

前言 经过我们所写的上一文章&#xff1a;微信小程序会议OA系统其他页面-CSDN博客 在我们的是基础面板上面&#xff0c;可以看到出来我们的数据是死数据&#xff0c;今天我们就完善我们的是数据 后台 在我们去完成项目之前我们要把我们的项目后台准备好资源我放在我资源中&…

element-table + el-pagination 实现分页多选功能

首先&#xff0c;在 el-table 中添加 :row-key“getRowKeys” 和 selection-change 事件&#xff08;当选择项发生变化时会触发该事件&#xff09;。然后&#xff0c;给多选框的一列添加 :reserve-selection“true”。 <!-- 表格 --> <div><el-table class&quo…

学习笔记---0基础+干货满满的单链表专题~~

目录​​​​​​​ 1. 链表的概念及结构&#x1f451; 1.1 什么是链表&#xff1f;&#x1f440; 1.2 为什么需要链表&#xff1f;⁉️ 1.3 链表的结构是怎么样的&#xff1f;❓ 2. 链表的分类&#x1f99c; 3. 实现单链表&#x1faf5; 3.1 要实现的目标&#x1f3af;…

分布式唯一Id,它比GUID好

分布式唯一Id&#xff0c;它比GUID好 一、前言 分布式唯一Id&#xff0c;顾名思义&#xff0c;是指在全世界任何一台计算机上都不会重复的唯一Id。 在单机/单服务器/单数据库的小型应用中&#xff0c;不需要用到这类东西。但在高并发、海量数据、大型分布式应用中&#xff0c…

JS加密/解密之闭包的运用

深入探讨JavaScript闭包的演变与应用 摘要&#xff1a; 本文将深入探讨JavaScript闭包的概念、特性以及其在实际开发中的应用。我们将从闭包的起源开始&#xff0c;探讨它在JavaScript编程中的重要性&#xff0c;并通过实例展示闭包在不同场景下的灵活应用。 引言 JavaScrip…

第一个Vue程序

首先下载vue.min.js或者vue.js Installation — Vue.js 在web文件下创建js文件并把vue.js复制到此文件。 创建一个jsp文件 显示界面

自然语言处理---Transformer机制详解之GPT模型介绍

1 GPT介绍 GPT是OpenAI公司提出的一种语言预训练模型.OpenAI在论文<< Improving Language Understanding by Generative Pre-Training >>中提出GPT模型.OpenAI后续又在论文<< Language Models are Unsupervised Multitask Learners >>中提出GPT2模型.…

langchain sql agent 案例

原文&#xff1a;SQL 数据库 |&#x1f99c;️&#x1f517; 朗链 (langchain.com)langchainSQL 数据库 |&#x1f99c;️&#x1f517; 朗链 (langchain.com) 说明&#xff1a;看原文&#xff0c;复制有问题 SQL Database This notebook showcases an agent designed to in…

如何使用visual studio 2010构建SQLite3.lib文件

sqlite3官网只提供了dll&#xff0c;并没有lib文件。需要自己生成sqlite3.lib。因项目升级到x64&#xff0c;以前并没有生成64位的链接库&#xff0c;需要自己创建。本人电脑操作系统windows 10, 开发环境为visual studio 2010。下面是详细生成过程。 1. 从源下载源&#xff08…

Spring中静态代理设计模式

目录 一、为什么需要代理设计模式 二、代理设计模式 三、静态代理设计模式 3.1 存在的问题 一、为什么需要代理设计模式 在项目的开发过程中我们知道service层是整个项目中最重要的部分&#xff0c;在service中一般会有两个部分&#xff0c;一个是核心业务&#xff0c;一个是额…

力扣每日一题54:螺旋矩阵

题目描述&#xff1a; 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2,3,6,9,8,7,4,5]示例 2&#xff1a; 输入&#…