解密C++中的forward<int>(a)和forward<int >(a):你真的了解它们之间的区别吗?

一文看尽C++中的forward完美转发

  • 一、前言
  • 二、深入理解forward和完美转发
  • 三、对`forward<int>(a)`的解析
  • 四、对`forward<int &&>(a)`的解析
  • 五、`forward<int>(a)`和`forward<int &&>(a)`的区别
  • 总结

一、前言

完美转发在C++中具有重要性,因为它允许函数将参数传递给其它函数,同时保持原始参数的值类别(左值或右值)和cv限定符(const或volatile)属性。这种机制对于实现通用的、灵活的代码至关重要,因为它可以确保函数接受的参数类型与其它函数调用相匹配,避免了不必要的参数类型转换。这种能力使得在编写泛型代码时,能够正确地传递参数并保持其原始属性,而不需要在函数调用链中添加多个重载或者模板函数来处理不同的参数类型。

在现代C++编程中,特别是在开发库和框架时,完美转发可以设计出更加灵活、通用的接口,提高代码的重用性和可维护性。同时还能够避免不必要的数据复制和额外的性能开销,提升代码的执行效率。因此,理解和正确应用完美转发是编写高效、高质量代码的关键。

本文的主旨是深入探讨C++中的forward<int>(a)forward<int &&>(a)之间的区别,以及它们在完美转发中的作用和应用。全面了解这两种形式的forward在C++中的具体用法、技术细节和差异,以及在实际开发中如何正确选择适当的形式进行参数传递和维护参数的值类别和cv限定符属性。

使用std::forward
入参
转发方法
目标对象

二、深入理解forward和完美转发

"forward"是C++语言中的一个重要概念,通常与"完美转发"相关联。在C++中,完美转发指的是在函数调用中保持参数的原始类型(左值或右值)和cv限定符(const或volatile)属性。使得函数可以将参数转发给其它函数,而不会丢失参数的原始属性

std::forward是C++标准库中的一个函数模板,用于在进行参数转发时保持参数的值类别和cv限定符。通常与模板参数的右值引用(T&&)结合使用,以实现完美转发。

完美转发和std::forward的主要用途

  1. 在实现通用函数时,允许函数将参数转发给其它函数,以保持参数的原始属性;
  2. 通过明晰地引入右值引用,避免因参数传递导致的不必要的数据拷贝,提高性能;
  3. 支持实现通用代码,以处理不同类型的参数和参数属性,实现更加灵活的解决方案。

在 C++ 中,每个表达式都有一个 值类别 和 一个 引用类别

  1. 值类别(Value Category):值类别描述了表达式产生的值的类别,以及该值是否可以被修改。C++ 中的值类别有两种:

    • lvalue:代表一个对象或者函数,可以取地址并且可以修改。
    • rvalue:代表一个临时对象,不可以取地址并且通常不可以被修改。
  2. 引用类别(Reference Category):引用类别描述了表达式的结果是一个引用还是一个值。C++ 中的引用类别有两种:

    • lvalue reference:产生一个 lvalue 引用,即可以被修改。
    • rvalue reference:产生一个 rvalue 引用,通常用于移动语义或者完美转发。

引用类别用于决定表达式的返回值是一个引用还是一个值,并且也与移动语义、完美转发等有关联。理解和使用值类别和引用类别可以更好地理解 C++ 中的对象生命周期、移动语义、函数重载匹配、完美转发等问题。

右值引用是实现完美转发的重要组成部分。右值引用是 C++11 中引入的一种引用类型,通过 && 符号声明。右值引用可以绑定到临时对象(即右值)或者具名的右值,而不能绑定到左值。右值引用在移动语义、完美转发等方面发挥着重要作用。

C++通过使用右值引用和模板参数,可以实现完美转发。标准库中提供了 std::forward 函数模板,它与右值引用结合使用,用于在函数调用中实现完美转发。

通过使用右值引用和 std::forward,可以在一个函数中将参数(包括左值和右值)完美地转发到另一个函数,同时保持参数的原始属性。使得函数可以接受任意类型的参数,并将其转发到其它函数,而不会失去参数的原始性质。

因此,右值引用为实现完美转发提供了一种重要的机制,通过结合使用右值引用和 std::forward,可以实现更加通用和灵活的函数模板,支持处理各种类型的参数并保持其原始属性。

三、对forward<int>(a)的解析

(1)forward<int>(a)的定义和用法。
std::forward 是一个模板函数,定义在 <utility> 头文件中。用于在函数模板中完美地转发参数,保持参数的原始类型和 cv 限定符属性。

函数原型:

template<class T>
constexpr T&& forward(typename std::remove_reference<T>::type& t) noexcept;template<class T>
constexpr T&& forward(typename std::remove_reference<T>::type&& t) noexcept;

std::forward 接受一个模板参数 T 和一个参数 t,用于完美转发参数 t 的类型为 T。通过调用 std::forward,参数 t 的引用类型(左值引用或右值引用)可以被正确地转发到另一个函数,以保持参数的原始类型和属性。

forward<int>(a) 中的 <int> 是模板参数,用于指定希望将参数 a 转发的类型为 int。例如,当 a 是一个左值时,forward<int>(a) 将保持 a 的左值特性,将 a 以左值引用的形式转发到另一个函数中。

示例:

void process(int&& x) {// 处理右值 x
}template<typename T>
void wrapper(T&& arg) {process(std::forward<T>(arg)); // 使用 std::forward 完美转发参数到 process 函数
}int main() {int a = 5;wrapper(a); // 调用 wrapper 函数,将参数 a 以左值的形式转发到 process 函数return 0;
}

调用 wrapper(a) 时,参数 a 被转发到 process 函数,并保持了其原始类型和属性。在 wrapper 函数中,std::forward 用于完美转发参数,确保将左值引用转发给了 process 函数。

(2)参数传递的机制。
参数传递的机制指的是将参数传递给函数或方法时,参数在内存中是如何被处理和访问的。常见的参数传递机制包括值传递、引用传递和指针传递。

  1. 值传递(Pass by Value):在值传递机制中,函数的参数是通过将其值拷贝到函数内部来进行传递的。所以,在函数内对参数的修改不会影响到原来的值。值传递适用于传递基本数据类型和小型对象,但对于大型对象来说,由于需要进行复制,会带来一定的性能开销。

  2. 引用传递(Pass by Reference):在引用传递机制中,函数的参数是通过引用(即内存地址)进行传递的,而不是进行值的拷贝。所以,在函数内部对参数的修改会直接影响到原始值。引用传递适用于需要在函数内修改参数值的情况,同时也能避免额外的复制开销。

  3. 指针传递(Pass by Pointer):指针传递与引用传递类似,但是函数的参数是通过指针进行传递的。与引用不同的是,指针需要显式地进行解引用操作来获取参数值。指针传递通常用于需要可选参数或者需要在函数内修改参数指向对象的情况。

四、对forward<int &&>(a)的解析

(1)forward<int &&>(a)的定义和用法。函数原型:

template<class T>
constexpr T&& forward(typename std::remove_reference<T>::type& t) noexcept;template<class T>
constexpr T&& forward(typename std::remove_reference<T>::type&& t) noexcept;

forward<int &&>(a)中,int && 表示正在使用模板函数 forward 来指示需要将参数 a 以右值引用的形式进行转发。

示例:

void process(int&& x) {// 处理右值 x
}template<typename T>
void wrapper(T&& arg) {process(std::forward<T>(arg));  // 使用 std::forward 完美转发参数到 process 函数
}int main() {int a = 5;wrapper(std::move(a));  // 调用 wrapper 函数,将参数 a 以右值引用的形式转发到 process 函数return 0;
}

通过std::move将参数 a 转换为右值,并将其作为参数传递给 wrapper 函数。然后,在 wrapper 中使用 std::forward 将参数以右值引用的形式转发到 process 函数中。这样就能在 process 函数中正确地处理右值引用参数。

(2)右值引用的特性:

  1. 右值引用可以绑定到临时对象(右值)。右值是指那些临时创建的、无法被引用的临时对象,例如函数返回值、临时对象、或者通过 std::move 转换的对象等。

  2. 可修改:通过右值引用可以对绑定的临时对象进行修改,并可以将其所有权(资源)转移给其他对象。这为实现移动语义提供了基础,使得在不需要进行深层拷贝的情况下可以高效地将对象传递给其他对象。

  3. 通过使用 std::forward 和模板推导,右值引用还可以用于实现完美转发(perfect forwarding),这样可以在函数模板中将参数以原始形式转发到其他函数,保持其原始类型和特性。

  4. 右值引用的引入可以实现移动语义,即将资源从一个对象“移动”到另一个对象,而不是进行昂贵的深层拷贝。对于动态分配的大型对象或资源管理类对象可以明显提高效率,例如 std::vector std::unique_ptr 等。

(3)对比 forward<int>(a)forward<int &&>(a)

  1. forward<int>(a):表示在使用 forward 函数模板时,要求参数 aint 类型进行转发。这会导致参数 a 被以左值引用类型进行转发。这样的转发方式适用于那些已经被声明为左值引用的变量或者表达式。

  2. forward<int &&>(a):表示要求参数 a 以右值引用类型进行转发。这样的转发方式适用于那些已经是右值引用的变量或者表达式,或者希望将参数以右值引用的形式进行转发,以便实现移动语义或者完美转发。

(4)应用场景:

  • 需要在函数模板中保持参数的原始类型和 cv 限定符属性,并且原始参数是一个左值引用时,使用 forward<int>(a) 来确保以相同的类型进行转发。

  • 需要在函数模板中对右值引用进行转发,保持其原始类型和 cv 限定符属性,并且假设原始参数是一个右值引用时,使用 forward<int &&>(a)。这在实现移动语义、完美转发等情况下非常有用。

(5)知识扩展: forward<int&>(a) 。使用 forward<int&>(a) 时要求参数 aint & 类型进行转发会导致参数 a 被以左值引用类型进行转发。这样的转发方式适用于那些已经是左值引用的变量或者表达式。原始参数是一个左值引用时,使用 forward<int&>(a) 来确保以相同的类型进行转发。

五、forward<int>(a)forward<int &&>(a)的区别

forward<int>(a)forward<int &&>(a) 的区别在于参数类型引用折叠以及模板参数推导上的处理方式。

  1. 参数类型:

    • forward<int>(a) 表示要求参数 aint 类型进行转发。即参数 a 被以左值引用类型进行转发。
    • forward<int &&>(a) 表示要求参数 aint && 类型进行转发。即参数 a 被以右值引用类型进行转发。
  2. 引用折叠:

    • 当参数 a 是一个左值时,forward<int>(a) 中的引用折叠会使参数 a 被以左值引用类型进行转发。
    • 当参数 a 是一个右值时,forward<int &&>(a) 中的引用折叠会使参数 a 被以右值引用类型进行转发。

forward<int>(a) 情况下编译器将使用模板参数推导,挑选出与 int 最匹配的类型,即值类型引用。也就是说,如果a是一个左值,它会被转发为一个左值引用,如果a是一个右值,它会被转发为一个右值引用。

forward<int &&>(a)使用了特殊的类型推导,会将参数a转发为右值引用。无论a是左值还是右值,它都会被转发为一个右值引用。

示例:

#include <iostream> 
using namespace std;template <class T> 
void Print(T &t) 
{ cout << "L" << t << endl; 
}
template <class T> 
void Print(T &&t) 
{ cout << "R" << t << endl; 
}
template <class T> 
void func(T &&t) 
{ Print(t); Print(std::move(t)); Print(std::forward<T>(t)); 
}int main() {cout << "-- func(1)" << endl; func(1); int x = 10; int y = 20; cout << "-- func(x)" << endl; func(x); // x本身是左值 cout << "-- func(std::forward<int>(y))" << endl; func(std::forward<int>(y)); cout << "-- func(std::forward<int&>(y))" << endl;func(std::forward<int&>(y));return 0; 
}

执行结果:

-- func(1)
L1
R1
R1
-- func(x)
L10
R10
L10
-- func(std::forward<int>(y))
L20
R20
R20
-- func(std::forward<int&>(y))
L20
R20
L20

总结

在C++中,forward<int>(a)forward<int &&>(a) 都是使用完美转发(forwarding)的技术,用于保持参数的值类别(左值或右值)。

  • forward<int>(a) 使用了模板参数推导,会将 aint 类型进行转发。无论传入的参数 a 是左值还是右值,它会保持其原始的值类别(即保持左值或右值属性)。

  • forward<int &&>(a) 也使用模板参数推导,但此时指定了参数类型为右值引用。

在这两种情况下,使用了std::forward,它在完美转发的过程中保留了传入参数的值类别,并将其正确地转发给新的函数。这在实现泛型代码时非常重要,能够确保正确地处理左值引用和右值引用。
在这里插入图片描述

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

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

相关文章

数据结构期末复习(2)链表

链表 链表&#xff08;Linked List&#xff09;是一种常见的数据结构&#xff0c;用于存储一系列具有相同类型的元素。链表由节点&#xff08;Node&#xff09;组成&#xff0c;每个节点包含两部分&#xff1a;数据域&#xff08;存储元素值&#xff09;和指针域&#xff08;指…

Python学习笔记之(一)搭建Python 环境

搭建Python 环境 1. 使用工具准备1.1 Python 安装1.1.1 下载Python 安装包1.1.2 安装Python 1.2 VScode 安装1.2.1 下载VScode安装包1.2.2 给VScode安装Python 扩展 2. 第一次编写Python 程序 本篇文章以Windows 系统为例。 1. 使用工具准备 1.1 Python 安装 1.1.1 下载Pytho…

双向循环链表实现C语言关键字中英翻译机 ฅ( ̳• · • ̳ฅ)

目录 1.双向循环链表的声明与定义&#xff1a; 2. 创建链表并对节点中的数据赋初值 3. 插入节点并链接 4.中英翻译 5. 小游戏的实现 6.菜单的实现 7. 释放内存 8.在主函数中用刚才定义的函数实现各种代码 输入样例&#xff1a; 实现方法&#xff1a;双向循环链表来实…

华为ensp网络设计期末测试题-复盘

网络拓扑图 地址分配表 vlan端口分配表 需求 The device is running!<Huawei>sys Enter system view, return user view with CtrlZ. [Huawei]un in en Info: Information center is disabled. [Huawei]sys S1 [S1]vlan 99 [S1-vlan99]vlan 100 [S1-vlan100]des IT [S1-…

万字长文谈自动驾驶occupancy感知

文章目录 prologuepaper listVision-based occupancy :1. [MonoScene: Monocular 3D Semantic Scene Completion [CVPR 2022]](https://arxiv.org/pdf/2112.00726.pdf)2. [Tri-Perspective View for Vision-Based 3D Semantic Occupancy Prediction [CVPR 2023]](https://arxiv…

跳跃表原理及实现

一、跳表数据结构 跳表是有序表的一种&#xff0c;其底层是通过链表实现的。链表的特点是插入删除效率高&#xff0c;但是查找节点效率很低&#xff0c;最坏的时间复杂度是O(N)&#xff0c;那么跳表就是解决这一痛点而生的。 为了提高查询效率&#xff0c;我们可以给链表加上索…

新手小白:一文带你用vite从零搭建企业级开发环境

在这工作的半年时间里&#xff0c;开始接触了前端开发&#xff0c;技术栈主要用的是 vue2&#xff0c;但是自己利用时间也学习了 vue3&#xff0c;组合式 api 和 vue3 的各种生态比 vue2 好用太多了&#xff0c;特别是状态管理库 pinia 比 vuex 简介很多&#xff0c;构建工具也…

rancher 手册

官方 https://www.rancher.com/https://github.com/rancher/rancherhttps://docs.rke2.io/ rancher kubernetesl yaml deploy rancher serverHelm Deploy Online Rancher DemoHelm & Kubernetes Offline Deploy Rancher v2.7.5 Demohelm upgrade rancher server from v2…

[Linux开发工具]——vim使用

Linux编辑器——vim的使用 一、什么是集成开发环境&#xff1f;二、什么是vim&#xff1f;三、vim的概念四、vim的基本操作五、vim命令模式命令集5.1 移动光标5.2 删除文字5.3 复制粘贴5.4 其他操作 六、vim底行模式命令集6.1 首先在命令模式下shift&#xff1b;进入末行模式。…

uni-app/vue封装etc车牌照输入,获取键盘按键键值

先看下效果如下&#xff1a; 动态图如下 uniapp的keyup获取不到keyCode和compositionstart&#xff0c;compositionend&#xff0c;所以需要监听input节点的keyup事件&#xff0c; 思路以及代码如下&#xff1a; 1.将每一个字符用文本框输入&#xff0c;代码如下 <view …

CEC2017(Python):粒子群优化算法PSO求解CEC2017(提供Python代码)

一、CEC2017简介 参考文献&#xff1a; [1]Awad, N. H., Ali, M. Z., Liang, J. J., Qu, B. Y., & Suganthan, P. N. (2016). “Problem definitions and evaluation criteria for the CEC2017 special session and competition on single objective real-parameter numer…

NullByte

信息收集 # nmap -sn 192.168.1.0/24 -oN live.nmap Starting Nmap 7.94 ( https://nmap.org ) at 2023-12-29 09:23 CST Nmap scan report for 192.168.1.1 Host is up (0.00038s latency). MAC Address: 00:50:56:C0:00:08 (VMware) Nmap scan report for …

PostgreSQL16.1(Windows版本)

1、卸载原有的PostgreSQL &#xfeff; &#xfeff; 点击Next即可。 &#xfeff;&#xfeff; 点击OK即可。 卸载完成。 2、安装 &#xff08;1&#xff09; 前两部直接Next&#xff0c;第二部可以换成自己想要安装的路径。 &#xff08;2&#xff09; 直接点击Next。…

政务大数据能力平台建设方案:文件全文30页,附下载

关键词&#xff1a;智慧政务解决方案&#xff0c;智慧政务建设&#xff0c;智慧政务服务平台&#xff0c;智慧政务大数据&#xff0c;数字政务一体化平台。大数据&#xff0c;政务大数据建设 一、智慧政务建设需求 1、政务服务需求&#xff1a;智慧政务建设需要满足人民群众的…

C++每日一练(8):图像相似度

题目描述 给出两幅相同大小的黑白图像&#xff08;用0-1矩阵&#xff09;表示&#xff0c;求它们的相似度。 说明&#xff1a;若两幅图像在相同位置上的像素点颜色相同&#xff0c;则称它们在该位置具有相同的像素点。两幅图像的相似度定义为相同像素点数占总像素点数的百分比。…

构建基础wlan网络 hcia无线

实验 旁挂组网 二层网络 ac为 dhcp的服务器给ap地址 s1给sta的ip地址 DHCP 业务为直接转发 实验步骤 第一步 poe 开启 poe en 开启 第二步 有线连接 vlan的配置 s1 vlan batch 100 101 连接的端口 port link-type trunk port trunk allow-pass …

pyDAL一个python的ORM(3)建表与表相关操作

1、建表操作define_table() 我们构建2张表&#xff0c;后面示例使用&#xff1a; db.define_table(person,#表名 Field(id, string),#字段名及字段的数据类型 Field(‘name, string), Field(‘dept, string), ) db.define_table(things, Field(id, string), Field(‘nam…

Docker之镜像上传和下载

目录 1.镜像上传 1) 先上百度搜索阿里云 点击以下图片网站 2) 进行登录/注册 3) 使用支付宝...登录 4) 登录后会跳转到首页->点击控制台 5) 点击左上角的三横杠 6) 搜索容器镜像关键词->点击箭头所指 ​ 编辑 7) 进入之后点击实例列表 8) 点击个人实例进入我们的一个…

win/linux 环境查看动态库包含的函数

我们打包了动态库&#xff0c;还要查看是否包含一些函数&#xff0c;需要导出这些函数 在win 环境下可以使用 .def 格式的文件进行操作 ######################################################### 跳过这一步&#xff0c;回到主题&#xff0c;在两个系统平台如何查看动态库包…

轻量封装WebGPU渲染系统示例<55>- 顶点数据更新

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/material/src/voxgpu/sample/VertexUpdateTest.ts 当前示例运行效果: ​​​​​​​ 此示例基于此渲染系统实现&#xff0c;当前示例TypeScript源码如下: export class VertexUpdateTest {pr…