C++学习笔记总结练习:C++完美转发

完美转发

1 概念

首先解释一下什么是完美转发,它指的是函数模板可以将自己的参数“完美”地转发给内部调用的其它函数。所谓完美,即不仅能准确地转发参数的值,还能保证被转发参数的左、右值属性不变。

template<typename T>
void function(T t) {otherdef(t);
}

function() 函数模板中调用了 otherdef() 函数。在此基础上,完美转发指的是:如果 function() 函数接收到的参数 t 为左值,那么该函数传递给 otherdef() 的参数 t 也是左值;反之如果 function() 函数接收到的参数 t 为右值,那么传递给 otherdef() 函数的参数 t 也必须为右值。

2 C++98实现完美转发

  • C++98通过左值引用和常量左值引用+函数重载实现完美转发

  • C++98/03 标准下的 C++ 也可以实现完美转发,只是实现方式比较笨拙。通过前面的学习我们知道,C++ 98/03 标准中只有左值引用,并且可以细分为非 const 引用和 const 引用。其中,使用非 const 引用作为函数模板参数时,只能接收左值,无法接收右值;而 const 左值引用既可以接收左值,也可以接收右值,但考虑到其 const 属性,除非被调用函数的参数也是 const 属性,否则将无法直接传递。

  • 如果使用 C++ 98/03 标准下的 C++ 语言,我们可以采用函数模板重载的方式实现完美转发

#include <iostream>
using namespace std;
//重载被调用函数,查看完美转发的效果
void otherdef(int & t) {cout << "lvalue\n";
}
void otherdef(const int & t) {cout << "rvalue\n";
}
//重载函数模板,分别接收左值和右值
//接收右值参数
template <typename T>
void function(const T& t) {otherdef(t);
}
//接收左值参数
template <typename T>
void function(T& t) {otherdef(t);
}
int main()
{function(5);//5 是右值int  x = 1;function(x);//x 是左值return 0;
}
// 程序执行结果为:
// rvalue
// lvalue

从输出结果中可以看到,对于右值 5 来说,它实际调用的参数类型为 const T& 的函数模板,由于 t 为 const 类型,所以 otherdef() 函数实际调用的也是参数用 const 修饰的函数,所以输出“rvalue”;对于左值 x 来说,2 个重载模板函数都适用,C++编译器会选择最适合的参数类型为 T& 的函数模板,进而 therdef() 函数实际调用的是参数类型为非 const 的函数,输出“lvalue”。

3 C++ 11实现完美转发。

  • C++ 11 标准中允许在函数模板中使用右值引用来实现完美转发

万能引用规则

  • C++11 标准中规定,通常情况下右值引用形式的参数只能接收右值,不能接收左值。但对于函数模板中使用右值引用语法定义的参数来说,它不再遵守这一规定,既可以接收右值,也可以接收左值(此时的右值引用又被称为“万能引用”)

  • 在 C++11 标准中实现完美转发,只需要编写如下一个模板函数即可

template <typename T>
void function(T&& t) {otherdef(t);
}

引用折叠规则

  • 此模板函数的参数 t 既可以接收左值,也可以接收右值。但仅仅使用右值引用作为函数模板的参数是远远不够的,还有一个问题继续解决,即如果调用 function() 函数时为其传递一个左值引用或者右值引用的实参。

  • C++ 11标准为了更好地实现完美转发,特意为其指定了新的类型匹配规则,又称为引用折叠规则(假设用 A 表示实际传递参数的类型):

    • 当实参为左值或者左值引用(A&)时,函数模板中 T&& 将转变为 A&(A& && = A&);
    • 当实参为右值或者右值引用(A&&)时,函数模板中 T&& 将转变为 A&&(A&& && = A&&)。
int n = 10;
int & num = n;
function(num); // T 为 int&
int && num2 = 11;
function(num2); // T 为 int &&

Foward模板函数

  • 引入了一个模板函数 forword(),我们只需要调用该函数,将函数模板接收到的形参连同其左、右值属性,一起传递给被调用的函数呢?
#include <iostream>
using namespace std;
//重载被调用函数,查看完美转发的效果
void otherdef(int & t) {cout << "lvalue\n";
}
void otherdef(const int & t) {cout << "rvalue\n";
}
//实现完美转发的函数模板
template <typename T>
void function(T&& t) {otherdef(forward<T>(t));
}
int main()
{function(5);int  x = 1;function(x);return 0;
}
// 程序执行结果为:
// rvalue
// lvalue
  • 总的来说,在定义模板函数时,我们采用右值引用的语法格式定义参数类型,由此该函数既可以接收外界传入的左值,也可以接收右值;其次,还需要使用 C++11 标准库提供的 forword() 模板函数修饰被调用函数中需要维持左、右值属性的参数。由此即可轻松实现函数模板中参数的完美转发。

1 问题定义

  • 完美转发就是将函数实参以其原本的值类别转发出去。转发值类别
void foo(int &)  { std::cout << "lvalue" << std::endl; }
void foo(int &&) { std::cout << "rvalue" << std::endl; }
template<typename /*T*/> void bar(/*T*/ x) { /*call foo with x*/ }int main() {int i;bar(i);  // expecting output: lvaluebar(1);  // expecting output: rvalue
}
  • 在这里,变量 i 经历了两次转发,所以我们需要先后解决这两次转发的值类别问题。
    • 用户调用 bar 时,参数的值类别问题当用户以左值表达式调用 bar 时,确保其实例化(Instantiation)的形参类型为左值引用当
    • 用户以右值表达式调用 bar 时,确保其实例化的形参类型为右值引用。
  • bar 调用 foo 时,参数的值类别问题
    • 当 bar 的形参类型为左值引用时,将其以左值转发给 foo
    • 当 bar 的形参类型为右值引用时,将其以右值转发给 foo

C++ 通过转发引用来解决第一个匹配,通过 std::forward 来解决第二个匹配。

2 转发引用

原理

  • 转发引用基于一个叫做引用坍缩(Reference Collapsing)的原理:

rvalue reference to rvalue reference collapses to rvalue reference, all other combinations form lvalue reference.typedef int& lref;

typedef int&& rref;
int n;
lref&  r1 = n; // type of r1 is int& ,  int&  + &  => int &
lref&& r2 = n; // type of r2 is int& ,  int&  + && => int &
rref&  r3 = n; // type of r3 is int& ,  int&& + &  => int &
rref&& r4 = 1; // type of r4 is int&&,  int&& + && => int &&

举例说明

void foo(int &)  { std::cout << "lvalue" << std::endl; }
void foo(int &&) { std::cout << "rvalue" << std::endl; }
template<typename T> int bar(T &&x);  // x is a forwarding referenceint main() {int i = 1;int &lref = i;int &&rref = 1;bar(1);                 // T 为 int,   decltype(x) 为 int&&bar(i);                 // T 为 int&,  decltype(x) 为 int&bar(lref);              // T 为 int&,  decltype(x) 为 int&bar(rref);              // T 为 int&,  decltype(x) 为 int&bar(std::move(rref));   // T 为 int,   decltype(x) 为 int&&bar<int &&>(1);         // T 为 int&&, decltype(x) 为 int&&
}

第一步转发问题解决

  • 解决了调用 bar 时参数值类别的问题,现在我们将 bar 的参数传递给
foo:void foo(int &)  { std::cout << "lvalue" << std::endl; }
void foo(int &&) { std::cout << "rvalue" << std::endl; }
template<typename T> int bar(T &&x) { foo(x); }

3 forward转发

forward原理

  • std::forward 的实现如下:
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); }template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"" substituting _Tp is an lvalue reference type");return static_cast<_Tp&&>(__t);
}
  • 实现了将左值转发为左值或右值,将右值转发为右值。

举例说明

void foo(const int &)  { std::cout << "lvalue" << std::endl; }
void foo(const int &&) { std::cout << "rvalue" << std::endl; }int main() {int i = 1;foo(std::forward<int>(i));     // output: rvalue; forward lvalue -> rvaluefoo(std::forward<int&>(i));    // output: lvalue; forward lvalue -> lvaluefoo(std::forward<int&&>(i));   // output: rvalue; forward lvalue -> rvaluefoo(std::forward<int>(1));     // output: rvalue; forward rvalue -> rvaluefoo(std::forward<int&>(1));    // error: static_assert failed due to requirement '!is_lvalue_reference<int &>::value' "can not forward an rvalue as an lvalue"foo(std::forward<int&&>(1));   // output: rvalue; forward rvalue -> rvalue
}

第二步转发的实现

void foo(int &)  { std::cout << "lvalue" << std::endl; }
void foo(int &&) { std::cout << "rvalue" << std::endl; }
template<typename T> int bar(T &&x) { foo(std::forward<T>(x)); }int main() {int i = 1;bar(i);  // output: lvaluebar(1);  // output: rvalue
}
  • 我们总结一下。完美转发问题是将函数的参数以其原本值类别转发出去的问题。转发引用 和 std::forward 共同解决了完美转发问题。其中,转发引用将函数的左值实参推导为左值引用类型,右值实参推导为右值引用类型。std::forward 将左值引用类型的实参转发为左值,将右值引用类型的实参转发为右值。

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

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

相关文章

前馈神经网络解密:深入理解人工智能的基石

目录 一、前馈神经网络概述什么是前馈神经网络前馈神经网络的工作原理应用场景及优缺点 二、前馈神经网络的基本结构输入层、隐藏层和输出层激活函数的选择与作用网络权重和偏置 三、前馈神经网络的训练方法损失函数与优化算法反向传播算法详解避免过拟合的策略 四、使用Python…

【HCIP】08.ISIS中间系统

链路状态协议&#xff0c;传递LSA信息ISIS基于数据链路层封装在OSI时&#xff0c;也有自己的网络层地址和自己的路由协议&#xff0c;即ISIS。之前的ISIS支持OSI的网络层地址&#xff0c;是为OSI中的CLNP&#xff08;无连接网络协议&#xff09;网络设计的路由协议&#xff0c;…

情人节特别定制:多种语言编写动态爱心网页(附完整代码)

写在前面案例1&#xff1a;HTML Three.js库案例2&#xff1a;HTML CSS JavaScript案例3&#xff1a;Python环境 Flask框架结语 写在前面 随着七夕节的临近&#xff0c;许多人都在寻找独特而令人难忘的方式来表达爱意。在这个数字时代&#xff0c;结合创意和技术&#xff0…

Ubuntu22.04编译Nginx源码

执行如下命令 # ./configure --sbin-path/usr/local/nginx/nginx --conf-path/usr/local/nginx/nginx.conf --pid-path/usr/local/nginx/nginx.pid输出结果&#xff0c;出现如下&#xff1a; Configuration summary using system PCRE2 library OpenSSL library is not used …

计算机视觉入门 3)最大池化

目录 一、最大池化最大池化进行压缩平移不变性 二、代码示例步骤2&#xff1a;图像读取转换步骤2&#xff1a;Filter & ReLU步骤3&#xff1a;Pool 一、最大池化 最大池化进行压缩 在Keras中&#xff0c;通过一个 MaxPool2D 层&#xff0c;将压缩步骤添加到之前的模型中&…

电脑找不到MSVCR120.dll怎么办?MSVCR120.dll是什么?

在我们的日常生活和工作中&#xff0c;电脑故障是难以避免的问题。而MSVCR120.dll文件是Windows系统中的一个重要组件&#xff0c;如果出现损坏或丢失&#xff0c;可能会导致程序无法正常运行&#xff0c;这个问题可能是由于系统文件损坏、病毒感染等原因导致的。因此&#xff…

Day17-Node后端身份认证-JWT

Day17-Node后端身份验证 一 密码加密 1 MD5加密 创建MD5.js//node提供了一个内置模块crypto用于密码加密 const crypto = require("crypto")module.exports.getMd5 = function(password){const md5

记录一次wordpress项目的发布过程

背景&#xff1a;发布一套已完成的代码到线上&#xff0c;有完整的代码包&#xff0c;sql文件&#xff0c;环境是linux 宝塔。无wordpress相关经验。 过程&#xff1a;正常的发布代码 问题1&#xff1a;访问自己的域名后跳转到别的域名。 解决&#xff1a; 修改数据表wp_optio…

Apipost中自定义接口字段如何配置

Apipost项目设置中可以配置接口文档中的自定义接口字段&#xff0c;创建状态码字典。分享分档时会展示到文档页面 状态码字典 在状态码字典中可以自定义状态码即其含义 自定义的状态码会在分享的API文档中展示 接口属性 接口属性中可以自定义接口和接口文档展示字段&#xf…

MySQL索引

目录 一、什么是索引 二、索引的原理 三、优缺点 四、分类 1、聚簇索引--顺序IO 2、非聚簇索引--随机IO 五、索引的设计原则 六、创建索引 1、创建表时创建索引 2、在已经存在的表上创建索引 3、使用ALTER TABLE语句来创建索引 1)普通索引 2&#xff09;唯一性索引 …

2308C++协程流程4

参考 #include <协程> #include "简异中.cpp" //用来中文定义的.构 任务{构 承诺型{任务 取中(){中{协柄<承诺型>::从承诺(*本)};}从不挂起 初挂起(){中{};}从不挂起 终挂起()无异{中{};}空 中空(){ 输出<<"取协程结果\n"; }//5空 对异…

蓝奥声智能工业安全用电监测与智慧能源解决方案

能源管理变得越来越重要。如今&#xff0c;能源成本已成为国内预算的核心因素&#xff0c;因此用电监控对大多数现代企业来说都很重要。许多企业在日常能源消耗监控中面临着一些挑战&#xff0c;因为它们的规模庞大&#xff0c;基础设施多样化&#xff0c;灵活性低&#xff0c;…

前端框架学习-React(一)

React 应用程序是由组件组成的。 react 程序是用的jsx语法&#xff0c;使用这种语法的代码需要由babel进行解析&#xff0c;解析成js代码。 jsx语法&#xff1a; 只能返回一个根元素 所有的标签都必须闭合&#xff08;自闭和或使用一对标签的方式闭合&#xff09; 使用驼峰式…

Java之包,权限修饰符,final关键字详解

包 2.1 包 包在操作系统中其实就是一个文件夹。包是用来分门别类的管理技术&#xff0c;不同的技术类放在不同的包下&#xff0c;方便管理和维护。 在IDEA项目中&#xff0c;建包的操作如下&#xff1a; 包名的命名规范&#xff1a; 路径名.路径名.xxx.xxx // 例如&#xff…

sql数据导出到excel

一、打开Navicat Premium 12 二、导出

R语言处理缺失数据(1)-mice

#清空 rm(listls()) gc()###生成模拟数据### #生成100个随机数 library(magrittr) set.seed(1) asd<-rnorm(100, mean 60, sd 10) %>% round #平均60&#xff0c;标准差10 #将10个数随机替换为NA NA_positions <- sample(1:100, 10) asd[NA_positions] <- NA #转…

ClickHouse(二十一):Clickhouse SQL DDL操作-临时表及视图

进入正文前&#xff0c;感谢宝子们订阅专题、点赞、评论、收藏&#xff01;关注IT贫道&#xff0c;获取高质量博客内容&#xff01; &#x1f3e1;个人主页&#xff1a;含各种IT体系技术&#xff0c;IT贫道_Apache Doris,大数据OLAP体系技术栈,Kerberos安全认证-CSDN博客 &…

大模型之BloomLLAMA----SFT(模型微调)

0. 简介 随着chatgpt的爆火&#xff0c;最近也有很多大模型在不断地出现&#xff0c;比如说Bloom系列以及以LLAMA为基础的ziya和baichuan。这些模型相较于chatglm来说&#xff0c;更加具有发展前景&#xff0c;因为其是完全可商用&#xff0c;并可以不断迭代更新的。最近作者在…

面试题 16.11. 跳水板

​题目来源&#xff1a; leetcode题目&#xff0c;网址&#xff1a;面试题 16.11. 跳水板 - 力扣&#xff08;LeetCode&#xff09; 解题思路&#xff1a; 使用哈希表记录所有可能结果&#xff0c;然后再将哈希表中的数据放入数组中&#xff0c;最后对数组进行排序即可。 解题…

Unrecognized Hadoop major version number: 3.0.0-cdh6.3.2

一.环境描述 spark提交job到yarn报错&#xff0c;业务代码比较简单&#xff0c;通过接口调用获取数据&#xff0c;将数据通过sparksql将数据写入hive中&#xff0c;尝试各种替换hadoop版本&#xff0c;最后拿下 1.hadoop环境 2.项目 pom.xml spark-submit \ --name GridCorr…