突破编程_C++_C++11新特性(完美转发 forward)

1 完美转发的概念

C++11 中引入的完美转发(Perfect Forwarding)是一种强大的编程技巧,它允许在编写泛型函数时保留参数的类型和值类别(即左值或右值),从而实现更为高效且准确地传递参数。这种技巧在编写包装器函数、代理函数或需要传递参数的函数模板时特别有用。

完美转发的核心概念在于,如果 function() 函数接收到的参数t为左值,那么该函数传递给 otherdef() 的参数 t 也应该是左值;反之,如果 function() 函数接收到的参数 t 为右值,那么传递给 otherdef() 函数的参数 t 也必须是右值。这意味着在转发过程中,参数的原始属性(包括其类型和值类别)都得到了保留。

为了实现完美转发,C++11 引入了右值引用和模板类型推导的概念。右值引用使用两个&符号(&&)来表示,它可以绑定到临时对象(右值)上,从而实现移动语义,避免不必要的拷贝操作。模板类型推导则允许编译器在编译时自动推断模板参数的类型。

在完美转发中,一个关键的概念是“万能引用”(universal reference)。对于形如 T&& 的变量或参数,如果 T 可以进行推导,那么 T&& 就称之为万能引用。万能引用既可以绑定左值,又可以绑定右值。这使得可以在函数模板中使用 T&& 作为参数类型,然后通过 std::forward 函数将参数以原始的形式转发给其他函数。

std::forward 函数是实现完美转发的关键。它的作用是将参数以原始的形式(包括其类型和值类别)转发给其他函数。std::forward 接受一个模板参数和一个参数,并返回该参数的转发引用。通过使用std::forward,我们可以确保在转发过程中不会丢失参数的原始属性。

2 引用折叠

2.1 引用折叠的基本概念

引用折叠是 C++11 中引入的一个重要概念,它在完美转发(Perfect Forwarding)中起到了关键作用。为了详细解释引用折叠的基本概念,需要再回顾一下左值引用和右值引用的概念。

左值引用是对一个已存在对象的引用,它可以使用取地址操作符&来获取地址。而右值引用则是对一个临时对象或者即将被销毁的对象的引用,通常用于支持移动语义,以提高代码性能。

引用折叠主要发生在模板函数和 std::forward 等上下文中。当在模板函数中使用 T&& 作为参数类型时,这里的 T&& 就是一个万能引用,它可以接收左值参数和右值参数。但是,仅仅依靠 T&& 并不能实现完美转发,还需要引用折叠规则来确保参数的原始属性(包括类型和值类别)在转发过程中得到保留。

引用折叠的一个典型应用场景是在实现完美转发时。通过结合 std::forward 和引用折叠,可以编写能够透明地转发参数的函数模板,确保参数在传递过程中的原始属性不会丢失。

2.2 引用折叠的规则与实例

引用折叠规则如下:

(1)左值引用与左值引用的折叠: 当左值引用与左值引用结合时,结果仍然是左值引用。具体来说,以下几种情况都会折叠为左值引用:

  • T& & 折叠为 T&
  • T& && 折叠为 T&
  • T& const & 折叠为 T& const(这里的 const 是保留的,表明引用本身是不可变的,但所引用的对象可能可以修改)

(2)右值引用与右值引用的折叠: 当右值引用与右值引用结合时,结果则是右值引用:

  • T&& && 折叠为 T&&
  • T&& & 折叠为 T&&

如下为样例代码:

#include <iostream>  
#include <type_traits>  template<typename T>
void print_type(T&&) {std::cout << typeid(T&&).name() << std::endl;if (std::is_lvalue_reference<T&&>::value) {std::cout << "left referance" << std::endl;}else if (std::is_rvalue_reference<T&&>::value) {std::cout << "right referance" << std::endl;}else {std::cout << "not referance" << std::endl;}
}int main() 
{int x = 5; // x 是左值  print_type(x); // 输出 int 的类型信息,并表明是左值引用  print_type(std::move(x)); // 输出 int 的类型信息,并表明是右值引用  print_type(12); // 输出 int 的类型信息,并表明是右值引用  return 0;
}

上面代码的输出为:

int
left referance
int
right referance
int
right referance

这个例子定义了一个模板函数 print_type,它接受一个万能引用 T&& 作为参数。通过调用 std::is_lvalue_reference<T>::value 和 std::is_rvalue_reference::value,可以检查 T 的类型是左值引用还是右值引用。

当传入一个左值 x 时,尽管 print_type 的参数类型是 T&&,但由于引用折叠规则,在模板实例化时,T 会被推导为 int&(左值引用)。同样地,当传入一个右值(如通过 std::move(x) 或字面量12)时,T 会被推导为 int,并且在函数内部,T&& 实际上是一个右值引用。

3 std::forward 函数

3.1 std::forward 函数的定义与作用

std::forward 是 C++11 中引入的一个模板函数,主要用于在模板函数或模板类中实现参数的完美转发(perfect forwarding)。完美转发是指函数或类模板可以将其参数原封不动地转发给另一个函数或类模板,同时保持被转发参数的左右值特性(lvalue 或 rvalue)。

定义:

std::forward的基本定义如下:

template<typename T>  
T&& forward(typename std::remove_reference<T>::type& arg);

这里,T 是一个模板参数,它代表了需要被转发的参数的原始类型。std::remove_reference<T>::type 是一个类型特性,用于从 T 中移除引用,从而得到原始类型。然后,接受一个这个原始类型的左值引用作为参数 arg。返回类型是 T&&,即 T 的右值引用。但这里通过引用折叠规则,当 arg 是左值时,返回的是左值引用;当 arg 是右值时,返回的是右值引用。

作用:

std::forward 的主要作用是在泛型编程中实现参数的完美转发。在 C++ 中,函数参数可以是左值引用(lvalue reference)或右值引用(rvalue reference)。左值引用类型的变量或参数可以修改其所绑定对象的值,而右值引用则通常用于移动语义,可以避免不必要的拷贝操作,提高性能。

当在模板函数中接收到一个参数时,此时并不知道这个参数是左值还是右值。但 std::forward 允许根据参数的原始类型(左值或右值)来转发它。这是通过引用折叠规则实现的。当将一个左值传递给 std::forward 时,它返回一个左值引用;当将一个右值传递给 std::forward 时,它返回一个右值引用。这样,就可以确保参数在转发过程中保持其原始的左右值特性。

在实际编程中,std::forward 通常与模板函数和右值引用结合使用。它可以帮助编写更加灵活和高效的泛型代码,因为开发人员可以根据参数的类型来决定是否进行拷贝操作或移动操作,从而提高程序的性能。

3.2 std::forward 函数的使用示例

如下一个 std::forward 具体的使用示例:

#include <iostream>  
#include <utility> // for std::forward  // 一个简单的函数,接受一个右值引用并打印信息  
void printValue(int&& value) {std::cout << "Rvalue: " << value << std::endl;
}// 一个简单的函数,接受一个左值引用并打印信息  
void printValue(int& value) {std::cout << "Lvalue: " << value << std::endl;
}// 一个模板函数,接受任何类型的参数并完美转发给printValue  
template<typename T>
void forwarder(T&& arg) {printValue(std::forward<T>(arg));
}int main()
{int x = 10; // 左值  forwarder(x); // 调用 void printValue(int& value) -> 左值引用参数forwarder(20); // 调用 printValue(int&& value) -> 右值引用参数 // 如果没有std::forward,会调用 void printValue(int& value) -> 左值引用参数return 0;
}

在这个示例中,forwarder 是一个模板函数,它接受一个通用引用参数 arg。在函数内部,使用 std::forward<T>(arg)来完美转发 arg 给 printValue 函数。注意,printValue 函数是重载函数,分别接受一个右值引用与一个左值引用作为参数。

3.3 完美转发的实现步骤

根据上面 “3.2 std::forward 函数的使用示例” 可以总结出完美转发的实现步骤:

(1)使用右值引用和模板类型推导

首先,需要在函数模板中使用右值引用和模板类型推导来接收参数。这通常通过 T&& 的形式来实现,其中 T 是一个模板类型参数。由于引用折叠规则,T&& 可以接收左值或右值参数,并将其推导为相应的左值引用或右值引用。

(2)调用 std::forward 进行转发

接下来,使用 std::forward 函数来转发参数。std::forward 的作用是根据传递给它的参数的原始值类别来构造一个相应类型的引用,并将其返回。这使得我们能够以参数原始的形式将其转发给另一个函数。

在调用std::forward时,我们需要同时提供模板类型参数T和实际的参数。这是因为 std::forward 需要知道参数的原始类型,以便正确地构造引用。

(3)编写转发函数

现在,可以编写一个转发函数,该函数接受任意类型和数量的参数,并使用 std::forward 将它们转发给另一个函数。转发函数的参数列表通常使用模板参数包和完美转发技术来实现。

转发函数的基本结构如下:

template<typename... Args>  
void forwarder(Args&&... args) {  targetFunction(std::forward<Args>(args)...);  
}

在这里,Args&&… args 表示接受任意数量和类型的参数,并使用完美转发技术将它们传递给 targetFunction。std::forward<Args>(args)… 则是使用 std::forward 来转发每个参数。

(4)调用转发函数

最后,可以调用转发函数,并将需要转发的参数传递给它。转发函数将使用 std::forward 将参数以原始的形式转发给目标函数。

注意事项

  • 完美转发要求目标函数能够接受与转发函数参数相同类型的参数,否则会出现编译错误。
  • 在使用完美转发时,需要确保转发函数的参数列表与目标函数的参数列表匹配,以便正确地进行转发。
  • 完美转发主要用于泛型编程和模板元编程中,可以帮助我们编写更加灵活和通用的代码。

4 完美转发的应用实例

4.1 完美转发在包装器函数中的应用

假设有一个需要被包装的函数,它接受任意类型的参数并处理它们:

#include <iostream>  
#include <string>  
#include <utility> // for std::forward  void processValue(int value) {  std::cout << "Processing int value: " << value << std::endl;  
}  void processValue(const std::string& value) {  std::cout << "Processing string value: " << value << std::endl;  
}

现在,创建一个包装器函数,它能够接受任意类型的参数,并使用完美转发将这些参数传递给上述的 processValue 函数:

#include <iostream>  
#include <utility> // for std::forward  // 包装器函数模板,接受任意类型和数量的参数  
template<typename... Args>  
void wrapperFunction(Args&&... args) {  // 使用完美转发将参数传递给processValue函数  processValue(std::forward<Args>(args)...);  
}  // 之前定义的processValue函数  
// ...  int main() {  // 调用包装器函数,并传递int类型的参数  wrapperFunction(12); // 输出: Processing int value: 12  // 调用包装器函数,并传递string类型的参数  wrapperFunction("Hello, World!"); // 输出: Processing string value: Hello, World!  return 0;  
}

在这个示例中,wrapperFunction 是一个模板函数,它接受任意数量和类型的参数(通过 Args&&… args 表示)。然后,它使用 std::forward<Args>(args)… 将参数完美转发给 processValue 函数。这样,无论传递给 wrapperFunction 的是左值还是右值,它们的值类别都会被保留并传递给 processValue,从而允许正确的重载版本被调用。

这个示例展示了完美转发在包装器函数中的一个典型应用,即允许包装器函数以透明的方式转发参数给另一个函数,同时保持参数的原始特性。这种技术使得编写通用和灵活的包装器函数变得更加容易。

4.2 完美转发在代理函数中的应用

完美转发在代理函数中的应用类似于上面的包装器函数,以下是一个简单的示例:

#include <iostream>  
#include <string>  
#include <utility> // for std::forward  // 目标函数,接受一个int类型的参数  
void targetFunction(int value) {std::cout << "Target function called with value: " << value << std::endl;
}// 代理函数模板,使用完美转发将参数传递给目标函数  
template<typename T>
void proxyFunction(T&& value) {targetFunction(std::forward<T>(value));
}int main() 
{// 调用代理函数,传递一个左值int  int lvalue = 12;proxyFunction(lvalue); // 输出: Target function called with value: 12  // 调用代理函数,传递一个右值int(临时对象)  proxyFunction(100); // 输出: Target function called with value: 100  return 0;
}

上面代码的输出为:

Target function called with value: 12
Target function called with value: 100

在这个示例中,proxyFunction 是一个模板函数,它接受一个参数 value,该参数的类型由模板参数 T 推导得出。通过使用 std::forward<T>(value),确保了 value 的原始值类别(左值或右值)在传递给 targetFunction 时保持不变。

当 lvalue(一个左值 int)被传递给 proxyFunction 时,由于使用了完美转发,targetFunction 接收到的仍然是一个左值引用。同样地,当传递一个右值(如字面量 100)时,targetFunction 接收到的也是一个右值引用(尽管在这个特定的例子中 targetFunction 的参数是一个值类型,不是引用类型,因此右值会被转换为左值。在实际应用中,targetFunction 可能会接受一个引用类型的参数以利用右值引用的优势,比如进行移动操作)。

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

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

相关文章

在springboot中利用Redis实现延迟队列

文章目录 前言一、基本思路二、springboot实现案例三、测试总结 前言 在开发过程中&#xff0c;有很多场景都需要用到延迟队列来解决。目前支持延迟队列的中间件也不少&#xff0c;特别是基于JMS模式下的消息中间件基本上都支持延迟队列。但是有时我们项目规模可能比较小&…

浅谈Spring框架

一、什么是Spring&#xff1f; Spring是一个开源框架&#xff0c;可以降低开发复杂度&#xff0c;提高开发效率&#xff0c;轻量级低耦合的框架。由于Spring的分层架构&#xff0c;可以自己选择整合其他组件&#xff0c;灵活性高 二、什么是IOC&#xff1f; IOC 叫做控制反转&…

如何在 Java 中造成内存泄漏?

如何在 Java 中造成内存泄漏&#xff1f; 应用程序创建一个长时间运行的线程&#xff08;或使用线程池来更快地泄漏&#xff09;。线程通过&#xff08;可选自定义&#xff09;加载类ClassLoader。该类分配一大块内存&#xff08;例如new byte[1000000]&#xff09;&#xff0…

Python PEP 8 代码风格指南

Python PEP 8 代码风格指南 0. 引言1. 空白字符2. 命名3. 表达式和语句4. 导入5. Pylint工具6. 要点总结 0. 引言 Python增强提案#8,也称作 PEP 8,是关于如何格式化Python代码的风格指南。 你可以按自己的方式编写Python代码,只要符合有效的语法规则。 然而,使用一致的风格可…

运维篇SHELL脚本实战案例

统计出每个IP的访问量有多少&#xff1f; 检查是否提供了日志文件的路径作为参数。使用awk从日志文件的每行中提取第一个字段&#xff08;假设这是IP地址&#xff09;。使用sort对提取的IP地址进行排序。使用uniq -c统计每个唯一IP地址的出现次数。最后&#xff0c;使用sort -…

一次消谐器在电力系统中的作用分析

一次消谐器是一种专门用于消除电力系统中的高次谐波的装置。它通过实时监测和分析系统中的谐波成分&#xff0c;采用先进的滤波技术&#xff0c;将谐波分量从系统中滤除&#xff0c;从而保持电力系统的稳定运行。 一次消谐器的主要作用体现在以下几个方面&#xff1a; 1. 保护电…

复习斐波那契(用C++写)

或者这样写&#xff1a; 斐波那契数列 题目描述 斐波那契数列是指这样的数列&#xff1a;数列的第一个和第二个数都为 1 1 1&#xff0c;接下来每个数都等于前面 2 2 2 个数之和。 给出一个正整数 a a a&#xff0c;要求斐波那契数列中第 a a a 个数是多少。 输入格式…

Java基础---IO流

1. File类 1.1 File的介绍 File是java.io.包下的类&#xff0c; File类的对象&#xff0c;用于代表当前操作系统的文件&#xff08;可以是文件、或文件夹&#xff09;。 注意&#xff1a;File类只能对文件本身进行操作&#xff0c;不能读写文件里面存储的数据。 1.2 File类…

Python模块-基础知识

Python模块-基础知识 1.模块分类&#xff1a; &#xff08;1&#xff09;自定义模块&#xff1a; 如果你自己写一个py文件&#xff0c;在文件内写入一堆函数&#xff0c;则它被称为自定义模块&#xff0c;即使用python编写的.py文件 &#xff08;2&#xff09;第三方模块&…

python初始化二维数据

1.遇到的问题 突然不知道什么原因&#xff0c;想起来实现一个矩阵的乘法&#xff0c;于是用python代码实现一下。 def matrix_multiply():a [[1, 2], [3, 4]]b [[5, 6, 7], [8, 9, 10]]m, n len(a[0]), len(b)if m ! n:print(we need a column equal b row!)m, t len(a),…

javaSE练习题(一)

1、BMI是根据体重测量健康的方式。通过以千克为单位的体重除以以米为单位的身高的平方计算出BMI。下面是16 岁以上人群的BMI图表: 编写一个java程序&#xff0c;提示用户输人以磅为单位的体重和以英寸为单位的身高&#xff0c;然后显示BMI值。注意: 1磅是0.453592 37千克而1英寸…

【工具类】adb常用命令

1. adb常用命令 1. adb常用命令 1.1. 常用命令1.2. 命令解析1.3. 参考资料 为了描述方便&#xff0c;假设需要通过 adb 操作 android 系统&#xff0c;本机是 ubuntu 系统 1.1. 常用命令 上传下载&#xff0c;/data/log 目录是手机上的目录&#xff0c;~/Downloads/log 是…

9大变频电源模块的测试参数及其重要性

变频电源是将交流电经过交流-直流-交流变换&#xff0c;从而得到输出为正弦波的交流电&#xff0c;广泛应用于家电、电机、电脑设备、测试单位、航空等领域。变频电源测试是确保系统稳定运行的重要步骤。 变频电源测试的重要参数 1. 输出电压和电流 可用万用表、电流表或者示波…

解决 Jupyter Notebook 中没有显示想要的内核的问题

如果在 Jupyter Notebook 的 “Kernel” 菜单中没有显示你想要的内核&#xff08;kernel&#xff09;&#xff0c;可能是因为该内核没有正确安装或配置到 Jupyter Notebook 中。在这种情况下&#xff0c;你可以尝试以下几个方法&#xff1a; 重新安装内核&#xff1a;首先&…

企业电脑如何管控(高效管控企业电脑的小技巧)

员工企业管理一直以来都是一个难题&#xff0c;难在人员多管理费劲。 因此高效管理一直都是企业最头疼的问题。 而使用一款软件辅助管理是很多企业发现的最有效的方法&#xff0c;如域智盾软件。 域智盾软件是一款专业的文件加密和数据安全软件&#xff0c;适用于各种企业和个…

C++异常处理

C异常处理 try和catch 在C中&#xff0c;try 是异常处理的关键字&#xff0c;用于定义一个代码块&#xff0c;该代码块中可能抛出异常。如果在 try 块中发生了异常&#xff0c;程序会立即停止当前块的执行&#xff0c;并查找与之匹配的 catch 块来处理异常。 下面是一个基本…

4.1 用源文件写汇编代码

汇编语言 1. 源程序 1.1 伪指令 汇编指令是有对应的机器码的指令&#xff0c;可以被编译为机器指令&#xff0c;最终为CPU所执行伪指令没有对应的机器指令&#xff0c;最终不被CPU所执行伪指令是由编译器来执行的指令&#xff0c;编译器根据伪指令来进行相关的编译工作 1.2…

【LeetCode每日一题】2312. 卖木头块(DFS记忆化搜索+动态规划)

文章目录 [2312. 卖木头块](https://leetcode.cn/problems/selling-pieces-of-wood/)思路1:用DFS进行记忆化搜索代码&#xff1a;思路2:动态规划代码&#xff1a; 2312. 卖木头块 思路1:用DFS进行记忆化搜索 1.要用DFS深度优先遍历每一种情况。在递归的同时&#xff0c;不断更…

【什么是Internet?网络边缘,网络核心,分组交换 vs 电路交换,接入网络和物理媒体】

文章目录 一、什么是Internet&#xff1f;1.从具体构成角度来看2.从服务角度来看 二、网络结构1.网络边缘1.网络边缘&#xff1a;采用网络设施的面向连接服务1.1.目标&#xff1a;在端系统之间传输数据1.2.TCP服务 2.网络边缘&#xff1a;采用网络设施的无连接服务2.1目标&…

unicloud快速上手,unicloud项目创建以及项目创建注意事项

uniCloud快速上手 本项目地址https://gitee.com/qayrup/unicloud-demo 创建unicloud项目 新建一个uni项目,并选择启用unicloud,选择阿里云或腾讯云 阿里云和支付宝云都支持一个月免费的云,如果只想体验啥的,可以选择这两个, 但是需要注意,支付宝云需要配置跨域,否则很多云函…