C++ 模板进阶知识——完美转发

目录

  • C++ 模板进阶知识——完美转发
    • 1. 完美转发的步骤演绎
      • 完美转发的关键点
    • 2. std::forward
      • 2.1 工作原理
      • 2.2 重要性
    • 3. 普通参数的完美转发
    • 4. 在构造函数模板中使用完美转发范例
    • 5. 在可变参数模板中使用完美转发范例
      • 5.1 常规的在可变参模板中使用完美转发
      • 5.2 将目标函数中的返回值通过转发函数返回给调用者函数
    • 6. 完美转发失败的情形一例
      • 其他可能导致完美转发失败的情况
    • 7. 完美转发在标准库中的应用
    • 总结

C++ 模板进阶知识——完美转发

完美转发是C++中一种高级的技术,用于在函数模板中转发参数至另一个函数,同时保持所有参数的值类别(左值、右值)和其他属性(如const修饰符)不变。这一技术主要通过模板和std::forward实现,并在泛型编程中尤为重要,因为它允许函数模板在不丢失任何参数信息的前提下传递参数。

1. 完美转发的步骤演绎

  1. 理解直接调用与转发的区别
    • 直接调用:函数直接从其它函数如main()中被调用。
    • 转发:函数通过另一个函数(通常是模板函数)将参数传递给第三个函数。
  2. 识别完美转发的需求
    • 在多层函数调用中,特别是当设计到模板函数作为"跳板"(即中间函数)时,保持参数的原始类型(包括其左值或右值特性)非常关键。
  3. 使用std::forward实现完美转发
    • std::forward是一个模板函数,它能够根据其模板参数的类型推断出传入参数的正确值类别。
    • 它只应在接受通用引用(形式为T&&,其中T是模板类型参数)的模板函数中使用。

示例说明:

考虑以下函数模板,它旨在将接收到的参数转发给另一个函数:

#include <iostream>// 定义 funcLast 接收 int 参数的函数
void funcLast(int& x) {std::cout << "Received an lvalue: " << x << std::endl;
}void funcLast(int&& x) {std::cout << "Received an rvalue: " << x << std::endl;
}template<typename T>
void funcMiddle(T&& param)
{// 使用 std::forward 确保 param 保持其原始类型特征funcLast(std::forward<T>(param));
}int main() {int a = 10;funcMiddle(a);  // 应输出: Received an lvalue: 10funcMiddle(20); // 应输出: Received an rvalue: 20
}

在这个例子中:

  • funcMiddle 使用了转发引用 T&& 来接收任何类型的参数。
  • std::forward<T>(param) 确保参数 param 以其原始的值类别(左值或右值)传递给 funcLast
  • funcLast 有两个重载版本,一个接收左值引用,另一个接收右值引用。这样可以根据传入的参数类型进行相应的处理。

这种方式确保了不论是左值还是右值,都能被 funcMiddle 正确地转发,并由 funcLast 接收并处理。这显示了完美转发在保持参数属性不变的同时,有效地将参数从一个函数传递到另一个函数的能力。

完美转发的关键点

  • 正确使用std::forward:仅在模板函数中,并且配合通用引用参数使用。
  • 避免额外的拷贝和移动操作:完美转发可以避免在函数间传递时不必要的拷贝和移动操作,优化性能。
  • 维护参数的完整性:确保参数的const属性和值类别在转发过程中不被改变,这对于编写通用代码库和API尤为重要。

完美转发使得函数模板可以作为灵活且高效的"中间人",在不破坏参数原有特性的情况下,将参数从一个函数传递到另一个函数。

2. std::forward

std::forward是C++11引入的一个模板函数,主要用于实现参数的完美转发。它的核心作用是在模板函数中保持参数的原始值类别(左值或右值)。std::forward通常与通用引用(Universal References,形式为T&&)一起使用,这种引用可以绑定到左值或右值上。通过使用std::forward,可以确保在函数模板中转发参数时,保持其左值或右值属性不变。

2.1 工作原理

std::forward 根据传入参数的类型在编译时确定返回左值引用或右值引用:

  • 当传递给std::forward的参数是一个左值时,std::forward返回一个左值引用。
  • 当传递给std::forward的参数是一个右值时,它返回一个右值引用。

这种行为使得std::forward非常适合用于函数模板中,尤其是那些需要根据参数原始类型将参数转发到其他函数的模板。

示例代码:

#include <iostream>void receive(int& x) {std::cout << "Lvalue received: " << x << std::endl;
}void receive(int&& x) {std::cout << "Rvalue received: " << x << std::endl;
}template<typename T>
void relay(T&& arg) {// 使用 std::forward 确保 arg 的值类别(左值或右值)被保持receive(std::forward<T>(arg));
}int main() {int lvalue = 10;relay(lvalue);  // 输出: Lvalue received: 10relay(20);      // 输出: Rvalue received: 20
}

在此代码中:

  • relay(lvalue):由于 lvalue 是左值,std::forwardarg 作为左值传递给 receive
  • relay(20):字面量 20 是右值,因此 std::forwardarg 作为右值传递。

2.2 重要性

std::forward的使用是现代C++中编写高效且类型安全代码的关键工具之一。它允许开发者编写可接受任何类型参数的泛型函数,并确保这些参数以最优的方式被处理(避免不必要的拷贝或移动操作),同时保持参数的原始属性不变。

总结来说,std::forward是实现完美转发的必备工具,它确保了参数在转发过程中保持其原始的左值或右值特性,从而使得函数模板可以更加灵活和高效地处理各种调用场景。

3. 普通参数的完美转发

使用std::forward可以确保普通参数在转发时保持其原始状态(左值或右值)。

#include <iostream>// 定义 funcLast 接收左值和右值重载版本
void funcLast(int& x) {std::cout << "Lvalue received in funcLast: " << x << std::endl;
}void funcLast(int&& x) {std::cout << "Rvalue received in funcLast: " << x << std::endl;
}// funcMiddle 使用模板和 std::forward 完美转发参数
template<typename T>
void funcMiddle(T&& param) {funcLast(std::forward<T>(param));
}int main() {int x = 10;funcMiddle(x);  // x 作为左值传递funcMiddle(20); // 20 作为右值传递
}

在这个示例中:

  • funcMiddle 接收一个通过通用引用传递的参数 param
  • 使用 std::forward<T>(param),该函数确保 param 的值类别(左值或右值)在调用 funcLast 时被保持。
  • 这样,无论是传递给 funcMiddle 的是左值还是右值,funcLast 都能接收到正确的值类别,并进行相应的处理。

4. 在构造函数模板中使用完美转发范例

在类模板的构造函数中使用完美转发可以有效地将构造参数直接转发给成员变量或基类的构造函数。

#include <iostream>
#include <string>template<typename T>
class MyClass {
public:T value;// 使用模板构造函数和完美转发来初始化成员变量template<typename U>MyClass(U&& val) : value(std::forward<U>(val)) {}void print() const {std::cout << "Value: " << value << std::endl;}
};int main() {std::string str = "Hello, World!";MyClass<std::string> obj1(str); // 传递左值MyClass<std::string> obj2(std::move(str)); // 传递右值obj1.print(); // 输出: Value: Hello, World!obj2.print(); // 输出: Value: Hello, World!
}

在这个示例中:

  • obj1 通过传递一个左值字符串 str 构造。
  • obj2 则通过传递 str 的右值(使用 std::move)构造。
  • 在两种情况下,MyClass 的构造函数都使用 std::forward<U>(val) 确保 val 的值类别在初始化 value 成员时得以保持。

5. 在可变参数模板中使用完美转发范例

5.1 常规的在可变参模板中使用完美转发

在 C++11 及以后的版本中,可变参数模板和完美转发一起使用,允许函数接收任意数量和类型的参数,并将它们无缝地转发到其他函数。这在编写包装器、委托或代理函数时特别有用。

#include <iostream>template<typename Func, typename... Args>
void wrapper(Func&& f, Args&&... args) {f(std::forward<Args>(args)...);
}void print(int a, double b, const std::string& c) {std::cout << "Int: " << a << ", Double: " << b << ", String: " << c << std::endl;
}int main() {std::string str = "example";wrapper(print, 42, 3.14159, str);
}

在这个示例中:

  • wrapper 函数接收一个函数 f 和一系列参数 args
  • 使用 std::forward<Args>(args)... 完美转发所有参数到函数 f
  • 这种方式确保了所有参数的值类别和类型在传递过程中保持不变。

5.2 将目标函数中的返回值通过转发函数返回给调用者函数

当使用完美转发在可变参数模板中转发函数调用时,也可以保留被调用函数的返回值类型,并将其返回给原始调用者。

#include <iostream>
#include <utility> // 包含 std::forwardtemplate<typename Func, typename... Args>
auto forwarder(Func&& f, Args&&... args) -> decltype(auto) {return f(std::forward<Args>(args)...);
}int add(int x, int y) {return x + y;
}int main() {int result = forwarder(add, 5, 3);std::cout << "Result of addition: " << result << std::endl;
}

在这个示例中:

  • forwarder 函数不仅转发参数,还转发了 add 函数的返回值类型。
  • 使用 decltype(auto) 自动推断返回类型,确保返回类型与 add 函数的返回类型完全一致。
  • 这种方法允许 forwarder 函数保持高度的灵活性和通用性,能够处理各种返回类型的函数。

6. 完美转发失败的情形一例

在 C++ 中,完美转发旨在将参数无缝地传递给另一个函数,同时保持参数的类型和值类别。然而,存在一些特殊场景,其中完美转发可能无法按预期工作,导致编译错误或运行时行为不正确。一个典型的例子是尝试使用 0NULL 作为指针来进行完美转发。

问题描述:

在 C++中,0NULL 常被用作空指针常量。然而,在模板函数中使用完美转发时,这些值会被推断为整数类型而非指针类型。这会导致类型不匹配的问题,特别是在函数重载解析中。

示例代码详解:

#include <iostream>void funcLast(int* ptr) {if (ptr) {std::cout << "Pointer is not null." << std::endl;} else {std::cout << "Pointer is null." << std::endl;}
}template<typename T>
void funcMiddle_Temp(T&& arg) {funcLast(std::forward<T>(arg));
}int main() {int* ptr = nullptr;funcMiddle_Temp(ptr);  // 正常工作,ptr 是 nullptr 类型funcMiddle_Temp(0);    // 编译错误,0 被推断为 int 而非 int* 类型funcMiddle_Temp(NULL); // 可能的编译错误,NULL 被推断为 int 而非 int* 类型
}

在这个示例中:

  • funcMiddle_Temp 接收 nullptr 时,一切正常,因为 nullptr 的类型是 nullptr_t,可以被正确推断并转发。
  • 然而,当传递 0NULL 时,由于它们可以被解释为整数,导致编译器无法将其推断为指针类型。这将引起编译错误,因为 funcLast 需要一个 int* 类型的参数。

解决方案

为了避免这种情况,建议使用 nullptr 来表示空指针,而不是 0NULL,因为 nullptr 在类型推断中表现更加明确和一致。

int main() {funcMiddle_Temp(nullptr); // 正确,nullptr 明确表示空指针
}

通过这个例子,可以看到在使用模板和完美转发时需要注意类型推断的问题。在设计接口和编写通用代码时,正确理解和应用类型安全的实践尤为重要,以避免潜在的错误和混淆。

其他可能导致完美转发失败的情况

  1. 重载的函数模板:当目标是重载的函数模板时,完美转发可能无法正确解析。
  2. 带有默认参数的函数:默认参数不会被完美转发。
  3. 位域作为模板参数:位域不能被无歧义地按引用传递。
  4. 初始化列表:完美转发无法处理初始化列表。

7. 完美转发在标准库中的应用

完美转发在C++标准库中得到了广泛应用,特别是在以下场景:

  1. std::make_uniquestd::make_shared:这些函数使用完美转发来将参数传递给对象的构造函数。
  2. std::emplace_back 和其他容器的 emplace 函数:这些函数使用完美转发来直接在容器中构造元素,避免不必要的拷贝或移动操作。
  3. std::thread 构造函数:使用完美转发来传递线程函数的参数。
  4. std::bind:使用完美转发来绑定函数参数。

总结

完美转发是现代C++中的一个关键特性,它允许我们编写更加通用和高效的代码。通过保持参数的原始类型和值类别,完美转发使得函数模板能够更加灵活地处理各种类型的参数,同时避免不必要的拷贝和转换操作。尽管在某些特殊情况下可能会失效,但在大多数情况下,完美转发是实现泛型编程和构建高性能库的强大工具。理解和正确使用完美转发对于编写现代C++代码至关重要。

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

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

相关文章

AF透明模式/虚拟网线模式组网部署

透明模式组网 实验拓扑 防火墙基本配置 接口配置 eth1 eth3 放通策略 1. 内网用户上班时间&#xff08;9:00-17:00&#xff09;不允许看视频、玩游戏及网上购物&#xff0c;其余时 间访问互联网不受限制&#xff1b;&#xff08;20 分&#xff09; 应用控制策略 2. 互联…

IBM Storwize V7000存储控制器故障节点报错574

背景&#xff1a;由于客户机房搬迁&#xff0c;需要下电迁移设备。该存储自2016年投入生产使用后&#xff0c;从未关过机&#xff0c;已正常运行七八年时间&#xff0c;期间只更换过硬盘&#xff0c;无其他硬件故障。 在GUI界面点击关闭系统后&#xff0c;大概等了40分钟&…

说一下解除docker限制内存警告

有时候docker要对容器使用的内存做出限制&#xff0c;通常的做法是使用参数 -m 例如&#xff1a; docker run -m 512M表示容器内存最大不能超过512M。 但这样做&#xff0c;在ubuntu会看到以下警告 WARNING: Your kernel does not support swap limit capabilitiesdocker官方…

从监控到智能:EasyCVR视频汇聚平台助力加油站安全监管升级转型

随着科技的不断进步&#xff0c;视频监控技术在各个行业的应用日益广泛&#xff0c;尤其在加油站这一关键领域&#xff0c;视频智能监管系统的应用显得尤为重要。TSINGSEE青犀视频EasyCVR视频汇聚平台作为一款基于“云-边-端”一体化架构的视频融合与AI智能分析平台&#xff0c…

JAVA读写Excel(jxl,poi,easyExcel)

目录 一、需求描述 二、具体操作Excel的常用方法 方法一&#xff1a; 使用jxl 方法二&#xff1a; POI 方法三&#xff1a;EasyExcel 三、总结 一、需求描述 前端有时候会传送 Excel 文件给后端&#xff08;Java&#xff09;去解析&#xff0c;那我们作为后端该如何实现…

Jenkins+Svn+Vue自动化构建部署前端项目(保姆级图文教程)

目录 介绍 准备工作 配置jenkins 构建部署任务 常见问题 介绍 在平常开发前端vue项目时,我们通常需要将vue项目进行打包构建,将打包好的dist目录下的静态文件上传到服务器上,但是这种繁琐的操作是比较浪费时间的,可以使用jenkins进行自动化构建部署前端vue 准备工作 准备…

AI模型的未来之路:全能与专精的博弈与共生

人工智能(AI)领域正迅速发展,伴随着技术的不断进步,AI模型的应用范围也在不断扩展。当前,AI模型的设计和使用面临两个主要趋势:全能型模型和专精型模型。这两者之间的博弈与共生将塑造未来的AI技术格局。本文将从以下七个方面探讨AI模型的未来之路,并提供实用的代码示例…

ROS1 + Realsense d455 固件安装+读取rostopic数据

目录 安装固件&#xff08;一定要匹配&#xff09;ROS1 wrapper 安装方法Realsense SDK 安装方法Realsense Firmware 安装方法 修改roslaunch配置文件&#xff0c;打开双目图像和IMU数据其他坑点参考链接 安装固件&#xff08;一定要匹配&#xff09; 如果你是使用ROS1获取rea…

Python数据分析-绘制图表

示例1&#xff1a; from pyecharts.charts import Bar # 柱状图 from pyecharts import options as optsfrom pyecharts.render import make_snapshotbar Bar() bar.add_xaxis([一月, 二月, 三月, 四月, 五月]) bar.add_yaxis("销售额", [10, 20, 15, 25, 30])# 配…

安卓framework单屏幕Display秒双/多屏互动相关需求改进-wms实战开发

背景 前面已经给学员朋友们分享过单屏秒变双屏的成果展示&#xff0c;具体详情可以点击这里&#xff1a; https://mp.weixin.qq.com/s/KdYTLMuXiBdjM0kZmYKzPg 一些vip学员朋友也纷纷求助马哥的实现patch代码&#xff0c;想用于公司的实战项目实现。但是在公司需求实现要求和…

Python(TensorFlow)和MATLAB及Java光学像差导图

&#x1f3af;要点 几何光线和波前像差计算入瞳和出瞳及近轴光学计算波前像差特征矩阵方法计算光谱反射率、透射率和吸光度透镜像差和绘制三阶光线像差图和横向剪切干涉图分析瞳孔平面焦平面和大气湍流建模神经网络光学像差计算透镜光线传播几何偏差计算像差和像散色差纠正对齐…

八、Maven总结

1.为什么要学习Maven&#xff1f; 2.Maven 也可以配华为云和腾讯云等。 3.IDEA整合Maven 4.IDEA基于Maven进行工程的构建 5.基于Maven进行依赖管理&#xff08;重点&#xff09; 6. Maven的依赖传递和依赖冲突 7. Maven工程继承和聚合 8.仓库及查找顺序

应用层协议Http

Http协议 1.1 什么是http协议 在进行网络通信时&#xff0c;应用层协议一般都是程序员自己写的&#xff0c;但是有一些大佬其实已经定义出了一些现成的应用层协议&#xff0c;例如&#xff1a;HTTP&#xff08;超文本传输协议&#xff09;、FTP&#xff08;文件传输协议&#…

SAP学习笔记 - 开发04 - Fiori UI5 开发环境搭建

上一章学习了 CDSView开发环境的搭建&#xff0c;以及CDSView相关的知识。 SAP学习笔记 - 开发03 - CDSView开发环境搭建&#xff0c;Eclipse中连接SAP&#xff0c;CDSView创建-CSDN博客 本章继续学习SAP开发相关的内容&#xff0c; - Fiori UI5的开发环境搭建 - 安装VSCode …

百度飞将 paddle ,实现贝叶斯神经网络 bayesue neure network bnn,aistudio公开项目 复现效果不好

论文复现赛&#xff1a;贝叶斯神经网络 - 飞桨AI Studio星河社区 https://github.com/hrdwsong/BayesianCNN-Paddle 论文复现&#xff1a;Weight Uncertainty in Neural Networks 本项目复现时遇到一个比较大的问题&#xff0c;用pytorch顺利跑通源代码后&#xff0c;修改至pad…

【Python报错已解决】 AttributeError: ‘move_to‘ requires a WebElement

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 前言一、问题描述1.1 报错示例1.2 报错分析1.3 解决思路 二、解决方法2.1 方法一&#xff1a;检查元素选择器2.2 方法…

828华为云征文|华为云Flexus X实例docker部署rancher并构建k8s集群

828华为云征文&#xff5c;华为云Flexus X实例docker部署rancher并构建k8s集群 华为云最近正在举办828 B2B企业节&#xff0c;Flexus X实例的促销力度非常大&#xff0c;特别适合那些对算力性能有高要求的小伙伴。如果你有自建MySQL、Redis、Nginx等服务的需求&#xff0c;一定…

一款支持同一个屏幕界面同时播放多个视频的视频播放软件

GridPlayer 是一款基于 VLC 的免费开源跨平台多视频同步播放工具&#xff0c;支持在一块屏幕上同时播放多个视频。其主要功能包括&#xff1a; 多视频播放&#xff1a;用户可以在一个窗口中同时播放任意数量的视频&#xff0c;数量仅受硬件性能限制。支持多种格式和流媒体&…

java实现,PDF转换为TIF

目录 ■JDK版本 ■java代码・实现效果 ■POM引用 ■之前TIF相关的问题&#xff08;两张TIF合并&#xff09; ■对于成果物TIF&#xff0c;需要考虑的点 ■问题 ■问题1&#xff1a;无法生成TIF&#xff0c;已解决 ■问题2&#xff1a;生成的TIF过大&#xff0c;已解决 …

vue3 自定义指令 directive

1、官方说明&#xff1a;https://cn.vuejs.org/guide/reusability/custom-directives 除了 Vue 内置的一系列指令 (比如 v-model 或 v-show) 之外&#xff0c;Vue 还允许你注册自定义的指令 (Custom Directives)。 我们已经介绍了两种在 Vue 中重用代码的方式&#xff1a;组件和…