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

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

文章目录

  • 模板中的右值引用(万能引用)、引用折叠与完美转发
    • 一、万能引用与引用折叠
      • 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中提出如此富有意义的新概念,极大地方便了我们重构代码,理想高效地编码实现功能。

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

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

相关文章

数据可视化第十天(爬虫爬取某瓣星际穿越电影评论,并且用词云图找出关键词)

开头提醒 本次爬取的是用户评论&#xff0c;只供学习使用&#xff0c;不会进行数据的传播。希望大家合法利用爬虫。 获得数据 #总程序 import requests from fake_useragent import UserAgent import timefuUserAgent()headers{User-Agent:fu.random }page_listrange(0,10) …

音视频入门基础:像素格式专题(3)——FFmpeg源码解析BMP格式图片的底层实现原理

音视频入门基础&#xff1a;像素格式专题系列文章&#xff1a; 音视频入门基础&#xff1a;像素格式专题&#xff08;1&#xff09;——RGB简介 音视频入门基础&#xff1a;像素格式专题&#xff08;2&#xff09;——不通过第三方库将RGB24格式视频转换为BMP格式图片 音视频…

创建一个vue3项目

## 1.创建命令 npm create vuelatest ## 2.具体配置 ## 配置项目名称 √ Project name: vue3_test ## 是否添加TypeScript支持 √ Add TypeScript? Yes ## 是否添加JSX支持 √ Add JSX Support? No ## 是否添加路由环境 √ Add Vue Router for Single Page Application de…

人工智能+量子计算:飞跃现实边界还是科技幻想?

人工智能量子计算&#xff0c;这是一种可能改变世界的伙伴关系。 在科技的前沿&#xff0c;两大革命性技术——人工智能&#xff08;AI&#xff09;和量子计算——正站在合作的十字路口。人工智能&#xff0c;以其强大的数据分析能力和模式识别&#xff0c;正在改变着我们生活…

传感器通过Profinet转Modbus网关与PLC通讯在生产线的应用

Profinet转Modbus&#xff08;XD-MDPN100/300&#xff09;网关可视作一座桥梁&#xff0c;能够实现Profinet协议与Modbus协议相互转换&#xff0c;支持Modbus RTU主站/从站&#xff0c;并且Profinet转Modbus网关设备自带网口和串口&#xff0c;既可以实现协议的转换&#xff0c…

前端基础入门三大核心之HTML篇:探索WebAssembly —— 开启网页高性能应用新时代

前端基础入门三大核心之HTML篇&#xff1a;探索WebAssembly —— 开启网页高性能应用新时代 WebAssembly基础概念工作原理概览WebAssembly实战示例基本使用 安全性与性能优化防范漏洞实践实际工作中的使用技巧结语与讨论 随着Web技术的飞速发展&#xff0c;前端开发者面临越来越…

全文检索ElasticSearch简介

1 全文检索 1.1 什么是全文检索 全文检索是一种通过对文本内容进行全面索引和搜索的技术。它可以快速地在大量文本数据中查找包含特定关键词或短语的文档,并返回相关的搜索结果。全文检索广泛应用于各种信息管理系统和应用中,如搜索引擎、文档管理系统、电子邮件客户端、新闻…

Mac虚拟机工具 CrossOver 24.0.0 Beta3 Mac中文版

CrossOver是一款在Mac上运行Windows应用程序的软件&#xff0c;无需安装虚拟机或重启计算机&#xff0c;简化了操作过程&#xff0c;提高了工作效率&#xff0c;为用户带来便捷体验。前往Mac青桔下载&#xff0c;享受前所未有的便利和高效。摘要由作者通过智能技术生成 CrossOv…

【FAQ】HarmonyOS SDK 闭源开放能力 —IAP Kit(2)

1.问题描述&#xff1a; 应用内支付IAP Kit和Payment Kit的区别以及适用场景&#xff1f; 解决方案&#xff1a; IAP Kit是四方支付&#xff0c;仅支持在线虚拟商品&#xff0c;如会员&#xff0c;游戏钻石等&#xff0c;双框架支持全球&#xff0c;目前单框架暂时只支持国内…

Qml:线程

import QtQuick import QtQuick.Controls Window {width: 640height: 480visible: truetitle: qsTr("Test Thread")//定时器测试//显示时钟Text {id: xtimex:parent.width-220y:parent.height-30text:"time"MouseArea{anchors.fill:parentonClicked:{timer…

【MySQL精通之路】SQL优化(1)-查询优化(8)-嵌套联接优化

主博客&#xff1a; 【MySQL精通之路】SQL优化(1)-查询优化-CSDN博客 上一篇&#xff1a; 【MySQL精通之路】SQL优化(1)-查询优化(7)-嵌套循环联接-CSDN博客 下一篇&#xff1a; 【MySQL精通之路】SQL优化(1)-查询优化(9)-外部联接优化-CSDN博客 与SQL标准相比&#xff0c…

robosuite导入自定义机器人

目录 目的&#xff1a;案例一&#xff1a;成果展示具体步骤&#xff1a;URDF文件准备xml文件生成xml修改机器人构建 目的&#xff1a; 实现其他标准/非标准机器人的构建 案例一&#xff1a; 成果展示 添加机器人JAKA ZU 7 这个模型 具体步骤&#xff1a; URDF文件准备 从…

python-docx 在word中指定位置插入图片或表格

docx库add_picture()方法不支持对图片位置的设置 1、新建一个1行3列的表格&#xff0c;在中间的一列中插入图片 from docx import Document from docx.shared import Pt from docx.oxml.shared import OxmlElement from docx.enum.text import WD_ALIGN_PARAGRAPHdef add_cen…

Nacos 进阶篇---Nacos服务端怎么维护不健康的微服务实例 ?(七)

一、引言 在 Nacos 后台管理服务列表中&#xff0c;我们可以看到微服务列表&#xff0c;其中有一栏叫“健康实例数” &#xff08;如下图&#xff09;&#xff0c;表示对应的客户端实例信息是否可用状态。 那Nacos服务端是怎么感知客户端的状态是否可用呢 &#xff1f; 本章…

Flutter 中的 AnimatedPhysicalModel 小部件:全面指南

Flutter 中的 AnimatedPhysicalModel 小部件&#xff1a;全面指南 Flutter 的 AnimatedPhysicalModel 是一个功能强大的小部件&#xff0c;它允许开发者创建具有物理效果的动画形状变换。这个小部件非常适合需要展示平滑过渡和动态交互的场景&#xff0c;如按钮按下效果、卡片…

Rust:多线程环境下使用 Mutex<T> 还是 Arc<Mutex<T>> ?

在 Rust 中&#xff0c;Mutex 本身不是线程不安全的&#xff1b;它提供了内部的线程同步机制。然而&#xff0c;如果你想在多线程环境中共享同一个 Mutex&#xff0c;你需要确保这个 Mutex 可以被多个线程访问。为此&#xff0c;你通常需要使用 Arc<Mutex<T>>。Arc…

几起 Linux 问题解决分享

&#xff08;首发地址&#xff1a;学习日记 https://www.learndiary.com/2024/05/linux-problems/&#xff09; 朋友们&#xff0c;大家好&#xff01;我是淘宝网学习日记小店专注于Linux服务领域的 learndiary&#xff0c;今天很高兴能与大家分享近期处理的一些Linux故障案例&…

数据库操作入门:PyMongo 和 MongoDB 的基本用法

MongoDB MongoDB是一种流行的NoSQL数据库&#xff0c;它将数据存储在类似JSON的文档中&#xff0c;使数据库非常灵活和可扩展。您可以在https://www.mongodb.com上免费下载MongoDB数据库&#xff0c;或者使用MongoDB云服务&#xff0c;访问https://www.mongodb.com/cloud/atla…

git会忽略我们工作改动中的大小写

在我们日常git工作中&#xff0c;我们对于文件名字的大小写修改正常是不会被git记录的 这是因为默认情况下git是不区分大小写的 这会导致一个问题&#xff0c;由于我们修改了文件名字的大小写&#xff0c;而对于文件之间相互依赖的导入代码没有对应修改 如果我们此时本地推送…