C++中的万能引用,引用折叠,完美转发

文章目录

  • 前提
  • 万能引用
    • 为什么需要万能引用
  • 引用折叠
  • 完美转发
    • `std::forward`
    • 基本用法
  • 参考链接

前提

在看别人写的一些库时,总是会碰见万能引用,引用折叠,完美转发这几个概念,这次对它们做一个详细的整理。

万能引用

万能引用 是一个特殊的引用类型,经常在模版编程中使用。它即可以绑定到左值,也可以绑定到右值,也就是说,使用万能引用的函数参数即可以接受左值,也可以接受右值

万能引用是通过以下条件识别的:

  1. 在函数模版中,参数类型是T&&的形式
  2. T是一个模版参数

当这两个条件都满足的时候,T&&被称为万能引用。万能引用的特别之处在于它可以绑定到左值、右值以及常量对象和非常量对象。

一个示例如下:

#include <iostream>
#include <utility> // for std::forward// 使用万能引用的模板函数
template<typename T>
void forwarder(T&& arg) {  // T&& 被称为万能引用std::cout << "num: " << arg << std::endl;
}int main() {int a = 42;forwarder(a);     // 即可以传递左值forwarder(42);    // 也可以传递右值return 0;
}

为什么需要万能引用

万能引用的主要用途是实现完美转发。接下来我们介绍完美转发。

引用折叠

在介绍完美转发之前,我们先介绍一个完美转发中的概念——引用折叠

一个模版函数,根据定义的形参和传入的实参的类型,我们可以有下面四种组合:

  1. 函数定义的形参类型是左值引用T&,传入的实参是左值引用&
  2. 函数定义的形参类型是左值引用T&,传入的实参是左值引用&&
  3. 函数定义的形参类型是右值引用T&&,传入的实参是左值引用&
  4. 函数定义的形参类型是右值引用T&&,传入的实参是右值引用&&

所有的引用折叠最终都代表一个引用,要么是左值引用,要么是右值引用。引用折叠的规则很简单:如果任一引用为左值引用,则结果为左值引用。只有两个都是右值引用,结果才为右值引用。

所以上述四种组合经过折叠后,结果如下:

  1. T& &折叠为T&
  2. T& &&折叠为T&
  3. T&& &折叠为T&
  4. T&& &&折叠为T&&

我们来看一个例子:

#include <iostream>// 泛型函数,展示引用折叠
template<typename T>
void referenceCollapsing(T&& arg) {// 打印参数类型if constexpr (std::is_lvalue_reference<decltype(arg)>::value) {std::cout << "arg is an lvalue reference" << std::endl;}else if constexpr (std::is_rvalue_reference<decltype(arg)>::value) {std::cout << "arg is an rvalue reference" << std::endl;}else {std::cout << "arg is not a reference" << std::endl;}
}int main() {int x = 42;referenceCollapsing(x);     // x 是左值,T 推导为 int&,折叠后arg的类型为 int&referenceCollapsing(42);    // 42 是右值,T 推导为 int, 折叠后arg的类型为 int&&return 0;
}

上面的这个例子应该能说明折叠引用的问题。

接下来看这份代码:

#include <iostream>
#include <utility>void process(int& x) {std::cout << "Processing lvalue: " << x << std::endl;
}void process(int&& x) {std::cout << "Processing rvalue: " << x << std::endl;
}template<typename T>
void forwarder(T&& arg) {process(arg);
}int main() {int a = 5;forwarder(a);    forwarder(10);   return 0;
}

简单分析下:forwarder模版函数使用了万能引用。在main函数中,调用了两次forward函数,一次传入的参数是左值a,一次传入的参数是右值10。同时重载了process函数,一个形参类型是int&,一个形参类型是int&&

代码运行的结果如下:

Processing lvalue: 5
Processing lvalue: 10

当传入参数为左值a时,运用引用折叠的规则,此时arg的类型为int&,所以调用process(int& x)
当当传入参数为右值10时,运用引用折叠的规则,此时arg的类型为int&&应该调用process(int&& x)

但是从结果来看,第二次调用了process(int& x)。为什么呢?
因为在forwarder的函数体内,虽然arg的类型为右值引用,但是它也有了变量名称,也可以取地址,所以当forwarder函数内部调用其它函数并将arg作为参数时,此时arg变为了右值。

那么如何解决这个问题呢?这就要用到完美转发了。

完美转发

完美转发(Perfect Forwarding)是C++11引入的一种技术,用于在函数模板中将参数“完美地”传递给另一个函数。这意味着参数的值类别(左值或右值)和所有修饰符(如 const、volatile 等)都能被保留,从而使被调用的函数能够正确处理参数。

std::forward

std::forward 是 C++11 引入的一个标准库函数模板,用于在泛型编程中实现完美转发(perfect forwarding)。它的主要作用是保持传入参数的值类别(左值或右值)不变地传递给另一个函数。

基本用法

测试代码如下:

#include <iostream>
#include <utility> // 包含 std::forward
#include <type_traits>// 普通函数重载,分别处理左值和右值
void process(int& x) {std::cout << "Processing lvalue: " << x << std::endl;
}void process(int&& x) {std::cout << "Processing rvalue: " << x << std::endl;
}// 泛型函数,用于转发参数
template <typename T>
void forwarder(T&& arg) {process(std::forward<T>(arg));
}int main() {int a = 5;forwarder(a);    // 传递左值forwarder(10);   // 传递右值return 0;
}

运行结果为:

Processing lvalue: 5
Processing rvalue: 10

这下就对了。

参考链接

  1. C++中的万能引用和完美转发

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

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

相关文章

leetcode-105. 从前序与中序遍历序列构造二叉树

题目描述 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], inorder [9,3,15,20,7] 输出: [3,9,20,null,nu…

重塑生态体系 深挖应用场景 萤石诠释AI时代智慧生活新图景

7月24日&#xff0c;“智动新生&#xff0c;尽在掌控”2024萤石夏季新品发布会在杭州举办。来自全国各地的萤石合作伙伴、行业从业者及相关媒体&#xff0c;共聚杭州&#xff0c;共同见证拥抱AI的萤石&#xff0c;将如何全新升级&#xff0c;AI加持下的智慧生活又有何不同。 发…

【WinDbg读取蓝屏的dmp日志】iaStorAC.sys 蓝屏解决

读取蓝屏日志&#xff1a; Window偶尔一次蓝屏不用管。 经常蓝屏重置或重装系统。 想要知道为什么蓝屏&#xff0c;通过WinDbg查看蓝屏日志。 蓝屏日志查找和配置 1&#xff0c;蓝屏那一刻拍照蓝屏的界面&#xff0c;即可知道基本的蓝屏信息。 2&#xff0c;蓝屏日志的配置…

从0开始搭建vue + flask 旅游景点数据分析系统(一):创建前端项目

根据前面的爬虫课程&#xff0c;我们重新开一个坑&#xff0c;就是基于爬取到的数据&#xff0c;搭建一个vueflask的前后端分离的数据分析系统 1 通过这个系列教程可以学习到什么&#xff1f; 从0开始搭建一个 vue flask 的数据分析系统&#xff1b;了解系统的整体架构&…

通信类IEEE会议——第四届通信技术与信息科技国际学术会议(ICCTIT 2024)

[IEEE 独立出版&#xff0c;中山大学主办&#xff0c;往届均已见刊检索] 第四届通信技术与信息科技国际学术会议&#xff08;ICCTIT 2024&#xff09; 2024 4th International Conference on Communication Technology and Information Technology 重要信息 大会官网&#xf…

Visual Studio调试Web项目

一、编译运行调试&#xff08;VS快捷键&#xff1a;CtrlF5&#xff09; 缺点&#xff1a;编译运行项目太慢&#xff0c;整体程序有些编译报错运行不了 二、附加到进程调试&#xff08;VS快捷键&#xff1a;CtrlAltP&#xff0c;选择w3wp.exe&#xff09; 无需编译&#xff0c;速…

Jvm是如何处理异常的

异常抛出 当Java程序运行时遇到无法处理的情况时,会抛出一个异常(比如在一个方法中如果发生异常),这时会创建一个异常对象,并转交给JVM,该异常对象包含异常名称,异常描述以及异常发生时应用程序的状态。创建异常对象并转交给JVM的过程称为抛出异常。 异常捕捉 当JVM检测…

设置混合精度训练(fp16),减少 GPU 内存使用并加快训练速度

你提供的代码片段是命令行参数解析器的一部分&#xff0c;用于设置混合精度训练&#xff08;fp16&#xff09;的参数。这些参数与 NVIDIA 的 Apex 库有关&#xff0c;该库提供了自动混合精度&#xff08;AMP&#xff09;训练功能&#xff0c;可以显著减少 GPU 内存使用并加快训…

数据结构之栈详解

1. 栈的概念以及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原则。 压栈…

7.23模拟赛总结 [数据结构优化dp] + [神奇建图]

目录 复盘题解T2T4 复盘 浅复盘下吧… 7:40 开题 看 T1 &#xff0c;起初以为和以前某道题有点像&#xff0c;子序列划分&#xff0c;注意到状态数很少&#xff0c;搜出来所有状态然后 dp&#xff0c;然后发现这个 T1 和那个毛关系没有 浏览了一下&#xff0c;感觉 T2 题面…

【嵌入式AI算法工程师软件清单】

嵌入式AI算法工程师软件清单 欢迎使用Markdown编辑器1. 嵌入式软件2. 嵌入式调试软件3. 嵌入式硬件4. 深度学习5. 软件综合 欢迎使用Markdown编辑器 你好&#xff01; 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇…

并发编程--volatile

1.什么是volatile volatile是 轻 量 级 的 synchronized&#xff0c;它在多 处 理器开 发 中保 证 了共享 变 量的 “ 可 见 性 ” 。可 见 性的意思是当一个 线 程 修改一个共享变 量 时 &#xff0c;另外一个 线 程能 读 到 这 个修改的 值 。如果 volatile 变 量修 饰 符使用…

day08:订单状态定时处理、来单提醒和客户催单

文章目录 Spring Task介绍cron表达式入门案例 订单状态定时处理需求分析代码开发扩展 WebSocket介绍入门案例特点 来单提醒需求分析和设计代码实现 客户催单需求分析和设计代码实现 Spring Task 介绍 Spring Task 是Spring框架提供的任务调度工具&#xff0c;可以按照约定的时…

谁说只有车载HMI界面?现在工业类的HMI界面UI也崛起了

谁说只有车载HMI界面&#xff1f;现在工业类的HMI界面UI也崛起了 引言 艾斯视觉作为行业ui设计和前端开发领域的从业者&#xff0c;其观点始终认为&#xff1a;工业自动化和智能化水平不断提高&#xff0c;人机界面&#xff08;Human-Machine Interface&#xff0c;简称HMI&a…

计网_物理层的传输媒体和设备

2024.07.05&#xff1a;计算机网络物理层的传输媒体和设备学习笔记 第3节 物理层的传输媒体和设备 3.1 传输媒体&#xff08;了解&#xff09;3.1.1 双绞线3.1.2 同轴电缆3.1.3 光缆&#xff08;1&#xff09;光纤特点 3.1.4 非导引型&#xff08;无线&#xff09;传输媒体&…

IO多路复用 epoll

特点 对于待检测集合select和poll是基于线性方式处理的&#xff0c;epoll是基于红黑树来管理待检测集合的。select和poll每次都会线性扫描整个待检测集合&#xff0c;集合越大速度越慢&#xff0c;epoll使用的是回调机制&#xff0c;效率高&#xff0c;处理效率也不会随着检测…

Django的一些魔改

介绍 Django和Django REST Framework都是功能很强大的框架,为我们的开发工作提供了极大的便利.但在某些特定需求下,难免存在一些限制和不便之处,为此我们需要进行一些自定义修改和拓展(魔改). 目录 介绍目录Django Remove default TableRemove is_staff 重写AdminSitepropert…

vue3编程-import.meta.glob实现动态路由(菜单)

import.meta.glob 是vite提供的批量懒加载组件的方法 本地开发环境&#xff1a; const modules import.meta.glob(../views/**/*.vue)这段代码返回的modules是一个Map&#xff1a; key是vue文件的相对路径&#xff0c;值是一个函数&#xff0c;将函数打印出来&#xff0c;如…

Github个人网站搭建详细教程【Github+Jekyll模板】

文章目录 前言一、介绍1 Github Pages是什么2 静态网站生成工具3 Jekyll简介Jekyll 和 GitHub 的关系 4 Mac系统Jekyll的安装及使用安装Jekyll的简单使用 二、快速搭建第一个Github Pages网站三、静态网站模板——Chirpy1 个人定制 四、WordPress迁移到Github参考资料 前言 23…

智能电表怎么算电费的?

智能电表作为现代电力管理系统的核心组成部分&#xff0c;通过先进的计量技术和通信手段实现了电费计算的自动化与精准化。本文将详细介绍智能电表的工作原理以及如何基于这些数据计算电费。 一、智能电表的工作原理 -数据采集&#xff1a;智能电表内置传感器持续监测电流、电…