C++ 新特性 | C++ 11 | std::forward、万能引用与完美转发

2024-06-15 1125阅读

文章目录

  • 一、std::forward、万能引用与完美转发
    • 1、万能引用
    • 2、类型推导
    • 3、引用折叠
    • 4、std::forward

      概述

      std::forward是C++11中引入的一个函数模板,用于实现完美转发。它的作用是根据传入的参数,决定将参数以左值引用还是右值引用的方式进行转发。

      C++ 新特性 | C++ 11 | std::forward、万能引用与完美转发
      (图片来源网络,侵删)

      传统上,当一个左值传递给一个函数时,参数会以左值引用的方式进行传递;当一个右值传递给一个函数时,参数会以右值引用的方式进行传递。完美转发是为了解决传递参数时的临时对象(右值)被强制转换为左值的问题。std::forward实现完美转发主要用于以下场景:提高模板函数参数传递过程的转发效率。

      一、std::forward、万能引用与完美转发

      1、万能引用

      对于形如T&&的变量或者参数,如果T可以进行推导,那么T&&称之为万能引用。换句话说,对于形如T&&的类型来说,其既可以绑定左值,又可以绑定右值,而这个的前提是T需要进行推导。最常见的万能引用方式如以下两种:

      函数模板:

      template
      void f(T&& param); // 存在类型推导,param是一个万能引用
      

      auto类型推导:

      auto&& var = var1; // 存在类型推导,var是一个万能引用
      

      注意:只有当发生自动类型推断时(例如:函数模板的类型自动推导),T &&才是万能引用。下面一个示例中的&& 并不是一个万能引用,例如:

      template
      void f( T&& param); // 这里T的类型需要推导,所以&&是一个 universal references
      template
      class Test {
      	Test(Test&& rhs);  // Test是一个特定的类型,不需要类型推导,所以&&表示右值引用  
      };
      

      2、类型推导

      万能引用进行类型推导时需要推导出T&&中的T的真实类型:若传入的参数是一个左值,则T会被推导为左值引用;而如果传入的参数是一个右值,则T会被推导为原生类型(非引用类型)。

      对于万能引用来说,条件之一就是类型推导,但是类型推导是万能引用的必要非充分条件,也就是说参数必须被声明为T&&形式不一定是万能引用。示例如下:

      template
      void func(std::vector&& t); // t是右值引用
      

      调用func时会执行类型推导,但是参数t的类型声明的形式并非T &&而是std::vector &&。 之前强调过,万能引用必须是T &&才行,因此,t是一个右值引用,如果尝试将左值传入,编译器将会报错:

      std::vector v;
      fun(v); // 编译错误,不能将左值绑定到右值
      

      形如const T&&的方式也不是万能引用:

      template
      void f(const T&& t); // t是右值引用
      int main() {
        int a = 0;
        f(a); // 错误
      }
      

      3、引用折叠

      引用折叠是一种特性,允许在模板元编程中使用引用类型的参数来创建新的引用类型。由于存在T&&这种万能引用类型,当它作为参数时,有可能被一个左值/左值引用或右值/右值引用的参数初始化,这需要通过类型推导,推导后得到的参数类型会发生类型变化,这种变化就称为引用折叠。

      根本原因是因为C++中禁止reference to reference,所以编译器需要对四种情况(& &、& &&,&& &,&& &&)进行处理,将他们折叠成一种单一的reference。引用折叠的规则如下:如果两个引用中至少其中一个引用是左值引用,那么折叠结果就是左值引用;否则折叠结果就是右值引用。示例如下:

      using T = int &;
      T& r1;  // int& & r1 -> int& r1
      T&& r2; // int& && r2 -> int& r2
        
      using U = int &&;
      U& r3;  // int&& & r3 -> int& r3
      U&& r4; // int&& && r4 -> int&& r4
      

      下面是一个具体的示例,可以看下对应的推导过程

      template
      void func(T &&t) {
          cout 
          int a = 1;
          int &b = a;
          func(a); // T 推导成 int &; T && == int & && == int &
          func(b); // T 推导成 int &; T && ==> int & && ==> int &
          func(1); // T 推导成 int; T && ==> int &&
          func(std::move(a)); // T 推导成 int &&; T && ==> int && && ==> int &&
          return 0;
      }
      

      4、std::forward

      完美转发是为了解决传递参数时的临时对象(右值)被强制转换为左值的问题,std::forward源码如下:

      template
      T&& forward(typename std::remove_reference::type& t) noexcept {
        return static_cast(t);
      }
      template 
      T&& forward(typename std::remove_reference::type&& t) noexcept {
        return static_cast(t);
      }
      

      其内部实现只有一行代码,即static_cast(t)使用static_cast进行类型转换,与std::move()实现方式类似。结合前面介绍的引用折叠,当接收一个左值作为参数时,std::forward()返回左值引用,相应的,当接收一个右值作为参数时,std::forward()返回右值引用。

      版本一:没有实现完美转发

      下面给出一个案例没有实现完美转发,如下:

      #include 
      template 
      void wrapper(T u) {
          fun(u);
      }
      class MyClass {};
      void fun(MyClass& a) { std::cout  std::cout  std::cout 
          MyClass a;
          const MyClass b;
          fun(a);
          fun(b);
          fun(MyClass());
          std::cout  // 万能引用
          func(std::forward};
      void func(MyClass& a) { std::cout  std::cout  std::cout 
          MyClass a;
          const MyClass b;
          func(a);
          func(b);
          func(MyClass());
          std::cout 
VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]