当前位置: 首页 > news >正文

模板中的右值引用(万能引用)、引用折叠与完美转发

模板中的右值引用(万能引用)、引用折叠与完美转发

文章目录

  • 模板中的右值引用(万能引用)、引用折叠与完美转发
    • 一、万能引用与引用折叠
      • 1. 模板中的右值引用
      • 2. 自动类型推导(auto)与万能引用
      • 3. 引用折叠与万能引用
      • 4. lambda表达式捕获
      • 5. 条件转发
      • 6. 类型萃取
    • 二、完美转发
    • 总结

一、万能引用与引用折叠

1. 模板中的右值引用

​ 我们经常听万能引用,什么是万能引用,与普通的右值引用有什么区别?

下面给出一个案例,利用模板函数参数中的右值引用来展现万能引用的用途:

首先给出一个自定义的类型,简易的实现出拷贝构造函数和移动构造函数同时不必关注是否正常实现了功能,增加打印信息以便我们可以通过控制台明晰该函数是否被调用,何时被调用:

class Date
{
public:Date(int year, int month, int day):_year(year), _month(month), _day(day){cout << "Date(int year, int month, int day)" << endl;}Date(const Date& d){cout << "Date(const Date& d)" << endl;}Date(Date&& d){cout << "Date(Date&& d)" << endl;}
private:int _year;int _month;int _day;
};

接着给出两个测试函数,函数参数类型分别常量左值引用和右值引用:

// 常量左值引用函数模板
template<typename T>
void func1(const T& val)
{T d(val);
}
// 万能引用参数函数模板
template<typename T>
void func2(T&& val)
{T d(std::forward<T>(val));cout << "val是右值引用:" << std::is_rvalue_reference<decltype(val)>::value << endl;
}

注意上面 func2() 函数中用到的 forward() 相当于 move() 的进阶版,可以保证在函数间参数传递时保持原有左/右值类型。

继续给出测试用例:

Date d(2000, 1, 2);cout << "*********左值参数********" << endl;
func1(d);
cout << "*************************" << endl;
func2(d);cout << "*********右值参数********" << endl;
func1(std::move(d));
cout << "*************************" << endl;
func2(std::move(d));

运行结果:

在这里插入图片描述

通过上图我们认识到万能引用在模板中的重要性,万能引用是模板中大多数情况保证移动构造函数被正常调用的重要条件。只有参数是万能引用时,函数内部才可调用 std::forward<T>() 完美转发变量的左右值类型,并将保持源类型的变量转发给其他函数。

至于我们为什么称其为“万能引用”,虽然常量左值引用 “const T& val” 也能兼容接收左值和右值参数,但它不支持移动语义,因为它不允许修改绑定的对象因此,万能引用更有优势,因为它既支持移动语义,又能与完美转发结合使用,成功传递变量及其类型。

2. 自动类型推导(auto)与万能引用

当使用auto关键字时,万能引用可以帮助推导变量的类型。例如,auto&&可以根据初始化表达式是左值还是右值来推导出正确的类型。

void test2()
{Date d(2000, 1, 2);auto&& d1(d);cout << "d1是左值引用:" << std::is_lvalue_reference<decltype(d1)>::value << endl;cout << "d1是右值引用:" << std::is_rvalue_reference<decltype(d1)>::value << endl;auto&& d2(std::move(d));cout << "d2是左值引用:" << std::is_lvalue_reference<decltype(d2)>::value << endl;cout << "d2是右值引用:" << std::is_rvalue_reference<decltype(d2)>::value << endl;
}

在这里插入图片描述

通过上图我们发现 auto&& 可以在推导表达式类型时,同时推导其左值性或右值性。

3. 引用折叠与万能引用

引用折叠的规则解决了引用的引用(C++中不允许)的问题。这里是引用折叠的基本规则:

  • 如果两个引用中至少一个是左值引用(&),那么结果是左值引用(&)。
  • 如果两个引用都是右值引用(&&),那么结果是右值引用(&&)。

首先给出一个简单的以万能引用为参数的模版函数:

template<typename T>
void func(T&& val) {// val 是一个万能引用auto p = &val;cout << "左值引用:" << std::is_lvalue_reference<decltype(val)>::value << endl;cout << "右值引用:" << std::is_rvalue_reference<decltype(val)>::value << endl;
}

测试函数:

void test3()
{int x = 10;int& lx = x; // lx 是 x 的左值引用int&& rx = 10; // rx 是一个右值引用func(x);  // T 推导为 int&,因此 val 的类型为 int& &,折叠为 int&cout << "x是右值引用:" << std::is_rvalue_reference<decltype(x)>::value << endl;cout << "-------------------" << endl;func(lx); // T 推导为 int&,因此 val 的类型为 int& &,折叠为 int&cout << "lx是右值引用:" << std::is_rvalue_reference<decltype(lx)>::value << endl;cout << "-------------------" << endl;func(rx); // T 推导为 int&&,因此 val 的类型为 int&& &&,折叠为 int&&cout << "xr是右值引用:" << std::is_rvalue_reference<decltype(rx)>::value << endl;cout << endl;func(move(x));cout << "move(x)是右值引用:" << std::is_rvalue_reference<decltype(move(x))>::value << endl;cout << "-------------------" << endl;func(10); // T 推导为 int,因此 val 的类型为 int&&
}

运行结果:

在这里插入图片描述

对上面运行结果部分地方需要着重做出解释:

在这里插入图片描述

4. lambda表达式捕获

void test4()
{auto x = 5;auto lambda = [y = std::move(x)]() mutable {y += 2;return y;};cout << x << endl;cout << lambda() << endl;
}

当调用 lambda() 时,将输出 7,因为 y 被初始化为 5 并且增加了 2。请注意,由于 x 被移动到 yx 的值在移动后未定义,但通常在实际编译器实现中,基本类型的值在移动后保持不变。因此,输出 x 的值仍然是 5。

5. 条件转发

template<typename T>
void forwarder(T&& arg) {if constexpr (std::is_lvalue_reference_v<T>) {//process(arg); // 处理左值}else {//process(std::move(arg)); // 处理右值}
}

在这个例子中,forwarder 函数使用万能引用 T&& 来接受任何类型的参数,并根据参数的类型来决定调用哪个 process 函数。

6. 类型萃取

类型萃取(Type Traits)是模板元编程中的一种技术,它允许你在编译时检查类型信息或者修改类型。万能引用可以与类型萃取结合使用,以确定传递给模板的参数类型的属性。

template<typename T>
void process(T&& arg) {using Type = typename std::remove_reference<T>::type;if constexpr (std::is_integral_v<Type>) {// 如果 T 是整数类型//handle_integral(std::forward<T>(arg));}else {// 如果 T 不是整数类型//handle_non_integral(std::forward<T>(arg));}
}

在这个例子中,process 函数使用类型萃取来移除引用,并检查 T 是否为整数类型。然后它使用 std::forward 来保持参数的值类别,并将其传递给相应的处理函数。

二、完美转发

在这里插入图片描述

​ 实际上这里才真正提起完美转发这个概念,看了本文前面内容也已经大概了解,我们这里仅仅将其抽离出来进行特别总结。

  • 首先,我们已经知道右值引用变量具有左值属性,因为其需要保留可修改性,所以自然不能是右值。

  • 其次,传递给右值引用类型的参数在传参后会退化为左值

所以我们想要在万能引用作参数的函数内部实现分离操作,需要对参数的左右值属性进行判断,例如:

template<typename T>
void print_right(T&& v)
{cout << "right -> " << v << endl;
}
template<typename T>
void print_left(const T& v)
{cout << "left -> " << v << endl;
}template<typename T>
void do_something(T&& val)
{if (std::is_rvalue_reference<decltype(val)>::value)	// val是右值{print_right(std::forward<T>(val));	// 利用完美转发将退化逆向为初始类型,此处判断初始右值,也可 move(val)}else	// val是左值{print_left(val);		// val退化为左值,直接传递}
}

测试函数:

void test5()
{int x = 18;//do_something<int>(x);		// 编译报错do_something<int&>(x);	// 注意这里x是左值,所以模板参数为int编译会报错,模板参数和函数参数类型需要统一满足引用折叠do_something<int>(10);
}

注意上面测试代码的案例也是引用折叠的重要体现

对于完美转发 forward(val) 中 val 的属性可能是左值也可能是右值:

  • 当 val 是左值时,forward 对左值 val 不做处理
  • 当 val 是右值时,forward 的作用相当于 move(val)

在这里插入图片描述


总结

​ 本文被三个词语所贯穿:万能引用、引用折叠、完美转发。这三个概念之间的联系紧密以及使用场景高度重合,正是因为C++11中提出如此富有意义的新概念,极大地方便了我们重构代码,理想高效地编码实现功能。

相关文章:

模板中的右值引用(万能引用)、引用折叠与完美转发

模板中的右值引用&#xff08;万能引用&#xff09;、引用折叠与完美转发 文章目录 模板中的右值引用&#xff08;万能引用&#xff09;、引用折叠与完美转发一、万能引用与引用折叠1. 模板中的右值引用2. 自动类型推导(auto)与万能引用3. 引用折叠与万能引用4. lambda表达式捕…...

Nacos启动报错:[db-load-error]load jdbc.properties error

在学习Nacos中间件时&#xff0c;出现了一个错误&#xff0c;竟然启动报错&#xff01;&#xff01;&#xff01;! 这个错误第一次遇见&#xff0c;当时我感觉大体就是--数据库连接方面的错误。 可是&#xff0c;对于初学者的我来说一脸懵啊&#xff1f;&#xff1f;&#xff…...

5.23相关性分析

相关性分析是一件很自然而然的事情&#xff0c;在生活中和科学研究中&#xff0c;我们都可能会不由自主地关注两件或者多件事情之间的联系。比如性别和方向感有没有关系&#xff0c;有多大关系&#xff0c;辨别不同事物时如何说明特征的科学性&#xff08;也就是该特征和事物的…...

使用 Sonatype Nexus Repository Manager 如何安装npm.md

1. 安装与启动 Nexus2. 登录 Nexus Web UI3. 创建 npm 仓库4. &#xff08;可选&#xff09;配置 npm 代理仓库5. 创建 npm 仓库组6. 配置 npm 客户端7. 测试和使用 Sonatype Nexus Repository Manager (通常简称 Nexus) 是一个强大的二进制管理系统&#xff0c;用于存储和管理…...

console如何连接远程机器上的java程序

启动参数 -Djava.rmi.server.hostname192.168.1.10 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port12345 -Dcom.sun.management.jmxremote.sslfalse -Dcom.sun.management.jmxremote.authenticatefalse2.jdk安装目录/bin下执行 go jconsole![在这里插入…...

高稳定数显芯片防干扰抗噪数码屏驱动高亮LED驱动IC-VK16K33A/AA 最大13×3的按键扫描

产品型号&#xff1a;VK16K33A/AA 产品品牌&#xff1a;永嘉微电/VINKA 封装形式&#xff1a;SOP28/SSOP28 原厂&#xff0c;工程服务&#xff0c;技术支持&#xff01; 概述 VK16K33A/AA是一种带按键扫描接口的数码管或点阵LED驱动控制专用芯片&#xff0c;内部集成有数据…...

Redis离线安装(单机)

目录 1-环境准备1-1下载redis-4.0.11.tar.gz1-2gcc环境 2-上传解压3-编译安装(需要gcc环境)4-配置redis5-启动Redis6-开启防火墙(root)7-添加开机启动脚本8-设置权限9-设置开机启动10-测试redis服务11-检查是否安装成功12-创建redis命令软连接13-测试redis14-必要时设置防火墙 …...

[Algorithm][动态规划][路径问题][不同路径][不同路径Ⅱ][珠宝的最高价值]详细讲解

目录 1.不同路径1.题目链接2.算法原理详解3.代码实现 2.不同路径 II1.题目链接2.算法原理详解3.代码实现 3.珠宝的最高价值1.题目链接2.算法原理详解3.代码实现 1.不同路径 1.题目链接 不同路径 2.算法原理详解 思路&#xff1a; 确定状态表示 -> dp[i][j]的含义 走到dp[…...

ChatGPT移动应用收入在GPT-4o发布后迎来最大涨幅

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…...

汉语拼音 如何 转化成粤语拼音 的

将汉语拼音&#xff08;普通话拼音&#xff09;转化为粤语拼音涉及到对声母、韵母以及声调的对照和调整。以下是详细的转换步骤和注意事项&#xff1a; 一、转换步骤 识别普通话拼音的声母和韵母查找对应的粤语拼音声母和韵母应用粤语声调 二、声母对照表 普通话拼音粤语拼…...

本地电子邮件测试工具-MailHog

通过MailHog&#xff0c;可以在浏览器中查看本机发的邮件内容&#xff0c;而无需发送到外网。 https://github.com/mailhog/MailHog在 macOS 环境下&#xff0c;下载文件后: 添加可执行权限:chmod x MailHog_darwin_amd64 运行:./MailHog_darwin_amd64 浏览器打开查看邮件:htt…...

Java18新特性

Java 18引入了若干新特性&#xff0c;以增强语言的功能性和性能。具体如下&#xff1a; 服务提供者接口&#xff08;Service Provider Interfaces, SPI&#xff09;&#xff1a;允许开发者为Java模块系统定义服务加载机制&#xff0c;从而能够更灵活地发现和加载服务实现。简单…...

大象资讯:PostgreSQL 17 Beta 1 发布!

↑ 关注“少安事务所”公众号&#xff0c;欢迎⭐收藏&#xff0c;不错过精彩内容~ PostgreSQL 全球开发小组 发布于 2024-05-23 PostgreSQL 全球开发小组宣布&#xff0c;PostgreSQL 17 的第一个测试版本现已可供下载。此版本包含 PostgreSQL 17 正式发布时将提供的所有功能的预…...

Rust:如何在 Windows 的 Linux 子系统(WSL)下安装

一、安装步骤 在Windows Subsystem for Linux (WSL)下安装Rust&#xff0c;可以按照以下步骤进行&#xff1a; 打开WSL终端&#xff1a; 首先&#xff0c;确保你的WSL已经安装并正常运行。你可以在Windows搜索栏中输入“WSL”并选择你安装的Linux发行版&#xff08;如Ubuntu&a…...

工具分享:VsCode注释神器,koro1FileHeader

他是有官方Wiki的。 https://github.com/OBKoro1/koro1FileHeader/wiki/ 项目在GitHub上开源。以下摘录部分wiki&#xff0c;用作介绍分享在这里插入代码片 如何找到setting.json设置模板 简单的输入命令 打开VSCode命令面板: mac: command p window: ctrl p输入> Ope…...

水面漂浮物生活垃圾识别检测系统

水面漂浮物生活垃圾识别检测系统通过现场监控摄像机对河道湖面等水体进行实时监测&#xff0c;水面漂浮物生活垃圾识别检测系统借助智能视频分析技术和YOLO深度学习技术&#xff0c;系统能够自动识别和抓拍水面上的垃圾漂浮物。一旦系统检测到有垃圾漂浮在水面上&#xff0c;立…...

通过python读取并发送二进制文件到串口

代码 #!python.exe """ filename send_bin.py brief According to the users input, read bin file, subpackage and send the file by UART. HowToUse send_bin.py -h author shadowThreeDgmail.com data 20220224 &q…...

前端笔记-day07

学成在线网站 文章目录 效果图代码展示index.htmlindex.cssbase.css 效果图 代码展示 index.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-w…...

【MySQL精通之路】INFORMATION_SCHEMA库-INNODB_METRICS表

INNODB_METRICS表提供了各种各样的INNODB性能信息&#xff0c;补充了INNODB性能模式表的特定重点领域。通过简单的查询&#xff0c;您可以检查系统的整体运行状况。通过更详细的查询&#xff0c;您可以诊断诸如性能瓶颈、资源短缺和应用程序问题等问题。 每个监视器表示InnoDB…...

React Native 之 定义全局状态管理库(九)

假设你正在使用基于单页面应用&#xff08;SPA&#xff09;的微前端框架。以下简化一个应用之间共享状态的例子。 1. 使用发布/订阅模式 // globalStateManager.js class GlobalStateManager { constructor() { this.subscribers {}; this.state {}; } subscribe(key…...

java线程池实战应用总结

一、线程池的创建方式 方式&#xff08;一&#xff09;&#xff1a;通过构造函数ThreadPoolExecutor()方式创建线程池 步骤1&#xff1a;先构建线程池 public class AsyncTaskExecutor {/*** 核心线程数*/private static final int corePoolSize 10;/*** 最大线程数*/priva…...

部署 harbor 创建私有项目

一在 Docker harbor 节点&#xff08;192.168.11.&#xff09;上操作 1 关闭防火墙防护 systemctl stop firewalld.service systemctl disable firewalld.service setenforce 0 2 安装docker yum install -y yum-utils device-mapper-persistent-data lvm2 yum-config-ma…...

在Linux系统中解决Java生成海报文字乱码和缺少字体文件的问题

在Linux系统中,如果缺少特定的字体文件,可以通过以下几种方法来解决: 1. 安装系统字体包 大多数Linux发行版提供了各种字体包,可以通过包管理器安装这些字体包。例如,在Debian/Ubuntu系统上,可以使用以下命令安装常见的字体包: # 安装基本的字体包 sudo apt-get updat…...

升级版网创教程wordpress插件自动采集并发布

主要功能&#xff1a; wordpress 插件主题系列支持自动采集并发布。 主要采集: 福缘&#xff0c;中创&#xff0c;冒泡 自动采集各大项目网进行整合发布到自己个人网站 插件话更新&#xff0c;减少网络请求&#xff0c;提升稳定性 代码完美开源 傻瓜式操作&#xff0c;一…...

MySQL 视图(1)

常用视图语句 -- 创建视图 CREATE VIEW t1_view AS SELECT * FROM t1; CREATE VIEW v AS VALUES ROW(1,2);-- 查询视图 SELECT * FROM t1_view;-- 查询视图的相关系统视图 SELECT VIEW_DEFINITION FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = test AND TABLE_NAME =…...

在排序数组中查找元素的一个位置和最后一个位置-力扣

第一此次想到的解法是首先使用二分查找在排序数组中查找到一个指定元素&#xff0c;随后对该元素左右进行遍历&#xff0c;找到起始位置和结束位置&#xff0c;代码如下&#xff1a; class Solution { public:vector<int> searchRange(vector<int>& nums, int…...

系统分析师-案例分析-数据库

系统分析师-案例分析-数据库 更多软考资料 https://ruankao.blog.csdn.net/ 文章目录 系统分析师-案例分析-数据库数据库考察知识点规范化函数依赖范式1NF2NF3NF 规范化问题不规范化反规范化设计反规范化设计同步问题 并发控制性能优化完整性约束视图安全分布式数据库特点优点…...

【RabbitMQ】使用SpringAMQP的消息队列(Hello Word)和工作队列(Work Queue)

SpringAMQP SpringAMQP中文文档 Hello Word **案例1&#xff1a;**利用SpringAMQP实现HelloWord中的集成消息队列功能 项目结构&#xff0c;如图&#xff1a; 1.引入AMQP依赖&#xff08;父工程中&#xff09; <!--AMQP依赖&#xff0c;包含RabbitMQ--> <dependen…...

项目集成SkyWalking,基于k8s搭建

一、搭建SkyWalking 官方文档&#xff08;英文&#xff09;&#xff1a;skywalking/docs at master apache/skywalking 中文可以使用&#xff1a;GitHub - SkyAPM/document-cn-translation-of-skywalking: [已过期,请使用官网AI文档] The CN translation version of Apache…...

mysql-差异备份流程

4.差异备份流程 差异备份流程&#xff08;重要) 第一次完整备份 innobackupex /xtrabackup innobackupex --userroot --password123456 /xtrabackup2024-05-23_20-25-05 第一次完整备份 2024-05-23_20-40-55 第二次差异备份 2024-05-23_20-47-37 第三次差异备份再往数据库里面…...