首页 > 编程语言 >C++ 完美转发

C++ 完美转发

时间:2023-10-11 14:13:09浏览次数:37  
标签:function 函数 右值 完美 C++ && 转发 模板

完美转发

定义

  • 完美转发指的是函数模板可以将自己的参数“完美”的转发给内部调用的其他函数中。
  • 所谓的完美,指的是不仅能准确的转发参数的值,还能保证被转发的参数的左、右值属性不变

解决的问题

  • 在 C++ 中如果想要将一个函数的参数转发给另一个函数时,需要保留原始参数的左右值属性。

完美转发的动机

  • 以下方的函数模板举例
template<typename T>
void function(T t){
    otherdef(t);
}
  • 在函数模板的 function() 中调用了 otherdef() 函数,在此基础上的完美转发指的是
    • 如果 function() 函数接收到的参数是左值,那么该函数传递给 otherdef() 的参数 t 也应该是左值
    • 反之,function() 函数接收到的参数是右值,那么该传递给 otherdef() 的参数 t 也应该是右值
  • 但是 function() 函数并不能做到完美转发,
    • 因为无论传入进来的 t 是左值还是右值,因为实参是非引用类型的,参数在传递过程中,会有一次额外的拷贝动作,也就意味着传入进来的 t 永远是左值,因为它是一个变量,在内存中有自己的存储地址
  • 如果将 t 的类型设置为 T& 类型,那么 function() 函数无法接收一个常量,即 function(5) 这种调用方式

函数模板重载方式实现完美转发

  • 利用函数重载,使用 const 接受右值
#include<iostream>

using namespace std;

void otherDef(int &t)
{
    cout << "left value" << endl;
}

void otherDef(const int &t)
{
    cout << "right value" << endl;
}

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

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

int main()
{
    function(5);

    int x = 1;
    function(x);

    return 0;
}
  • 这种方法会有以下几种问题:
    • 仅适合模板函数仅有少量参数的情况,否则就需要大量的重载函数模板,造成大量的代码冗余
    • 接受的右值只能读,不能修改,使用场景受限

完美转发的实现

  • 完美转发的实现可以由万能引用加上引用折叠以及 std::forward 三者共同作用完成

万能引用

  • 定义

    • C++ 标准中规定,右值引用形式的参数只能接收右值,不能接受左值。
    • 但是对于函数模板中使用右值引用语法定义的参数来说,它既可以接受左值,又可以接受右值,此时这个右值引用被称为万能引用
  • 代码

    • C++ 11 标准中,实现完美转发代码如下
    • 上面的模板代码的参数 t 既可以接受左值,也可以接受右值。
// version_1
template<typename T>
void function(T&& t)
{
    otherDef(t);
}

引用折叠

引入原因

int n = 10;
int &num = n;
function(num);  // 1

int &&num2 = 10;
function(num2);     // 2
  • 1 处的函数调用底层变成了 function(int &&t)
  • 2 处的函数调用底层变成了 function(int && &&t)
  • C++ 11 之前版本是不支持这种用法的

解决方法

  • C++ 11 为了更好的实现完美转发,特意为其指定了新的类型匹配规则,又称折叠规则
    • 当实参为左值或者左值引用(A&)时,函数模板中的 T&& 将转变为 A&,即 A& && = A&
    • 当实参为右值或者右值引用(A&&)时,函数模板中的 T&& 将转变为 A&&,即 A&& && = A&&
  • 即在实现完美转发时,只要函数模板的参数类型为 T&&,那么 C++ 就可以自行的推断出实际传入的实参是左值还是右值

std::forward

引入原因

  • 转发的函数模板 function() 已经解决了如何识别函数传入实参是左值还有右值类型的问题。
  • 但是对于函数内部,因为接收外部实参的函数参数在内存空间中有了地址,即 T&& t 这个 t 在 function 这个函数中始终是一个左值
  • 那么如何将 function() 接受的实参的类型与数值一同传递给内部要调用的函数呢?也就是 std::forward() 的作用

使用

  • 只要在模板函数中,调用 std::forward() 函数,就可以将传入参数的属性和数值一同传递到内部的调用函数中
  • 最终版的完美转发代码示例如下
#include<iostream>

using namespace std;

void otherDef(int &t)
{
    cout << "left value" << endl;
}

void otherDef(const int &t)
{
    cout << "right value" << endl;
}

template<typename T>
void function(T&& t)
{
    otherDef(std::forward<T>(t));
}

int main()
{
    function(5);

    int x = 1;
    function(x);

    return 0;
}

应用场景

  1. 转发函数参数
  2. 实现可变参数模板
  3. 通用库代码中处理各种类型的参数

标签:function,函数,右值,完美,C++,&&,转发,模板
From: https://www.cnblogs.com/wanghao-boke/p/17756937.html

相关文章

  • Qt_C++读写NFC标签Ntag支持windows国产linux操作系统
    本示例使用的发卡器:ntag2标签存储结构说明#include"mainwindow.h"#include"./ui_mainwindow.h"#include<QDebug>#include"QLibrary"#include"QMessageBox"//本示例可在windows、linux系统内编译、运行//判断windows、linux系统,声明动态库函数---------------......
  • C++小代码
    用于实现一个简单的学生信息管理系统:#include<iostream>#include<vector>#include<string>classStudent{private:std::stringname;intage;std::stringmajor;public:Student(std::stringname,intage,std::stringmajor){......
  • c++ 右值引用
    左值和右值左值可以理解为可以取地址的对象,右值指的除左值外的值,这个地方不必过于纠结左值引用就是常见的&+变量名右值引用当引用一个变量时可以使用intA=10;int&a=A;//这里就是左引用但是有时我们需要引用一个立即数怎么办int&a=10;//errorconstint&a=10;//rig......
  • c++对象模型学习笔记
    参照大佬的博客学习了一下c++的对象模型:https://www.cnblogs.com/skynet/p/3343726.html有些思考需要做下记录。对于有虚函数表的类的对象,它的起始地址处会存储vptr指向虚函数表,在这个虚函数表的前4或8字节中,会存储一个地址值,指向RTTI类型信息对于没有虚函数表的类的对象,也就......
  • C++ libwebsockets搭建WebSocket服务端及Http客户端、服务端
    https://blog.csdn.net/fantasysolo/article/details/88908948  概念WebRTCWebRTC,名称源自网页即时通信(英语:WebReal-TimeCommunication)的缩写,是一个支持网页浏览器进行实时语音对话或视频对话的API。它于2011年6月1日开源并在Google、Mozilla、Opera支持下被纳入万维网联......
  • C++测试技能
    googletest?gtestdoetest?doctest的引入和编译问题。编译和引入、api的使用会影响测试开发的体验功能是否丰富(对模板等测试)影响到测试的结果  这里的两个subcase的执行是并行的,都是先从头去执行一次vector的构造然后再挑选一个subcase进入TEST_CASE("vectorscanbesi......
  • C++11之类型转换(2千字长文详解)
    C++11之类型转换C语言中的类型转换在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换。隐式类型转化:编译器在编译阶段自动进行,能转就......
  • 【C++ Primer】变量和基本类型
    目录二、变量三、复合类型四、const限定符五、处理类型六、自定义数据结构一、基本内置类型 1、算术类型分为整形和浮点型,常见的算术类型:char:1个字节,short:2个字节,int:2个字节,long:4个字节,float:4个字节,double:8个字节,longlong【c++11】:8个字节。可寻址的最小内存块称为“字节(byte)”,......
  • 【C++ Primer】表达式
    一、基础1、左值和右值:当一个对象被用作右值的时候,用的是对象的值(内容);当对象用作左值的时候,用的是对象的身份(在内存中的位置)。   需要用到左值的地方有:赋值运算符需要一个左值作为其左侧运算对象,得到的结果也是左值。取地址符作用与左值对象,返回的指针是右值。解引用和下标运算......
  • 【C++ Primer】字符串和数组
    一、命名空间的using声明1、using声明:usingnamespace::name,例如:usingstd::cin。一旦声明了上述语句,就可以直接访问命名空间的变量。每个变量都需要using声明,位于头文件中的代码不应该使用using声明。2、using编译:usingnamespacestd;直接使用整个命名空间。使用using声明比使用us......