C/C++学习记录:std::forward 源码分析 / 完美转发的作用

本文详细分析了C++标准库中的std::forward源码,探讨了引用折叠和完美转发的概念。通过实例展示了std::forward如何在左值和右值引用间转换,确保值属性不变。文章还解释了在什么情况下需要使用std::forward,如在中间转发操作中保持原始类型,以提高效率。最后,总结了std::forward在模板编程中的重要作用,并指出未来学习模板技术的方向。
  • 抽空扣一点感兴趣的标准库源码,这里总结一下 std::forward() 相关的分析
  • 本文中 gcc version: 8.4.1 20200928 (Red Hat 8.4.1-1) (GCC)
  • 其中c++库安装路径为 /usr/include/c++/8

一、前言

  本文是基于前文 C/C++学习记录:std::move 源码分析 进行的。前文中分析了 std::move 的源码,其中涉及到的一些东西,比如说:

  1. 类型提取结构体 std::remove_reference
  2. C++标准转换运算符 static_cast
  3. 引用折叠的概念

  上面这三个东西在 std::forward() 中也有应用,因此在本文中也就不过多分析,这些东西的分析在上文中均可找到。点我跳转

  本文中仅记录上文中未提及的关于 std::forward() 的思路和写法,以及个人对完美转发这一概念的理解。

二、源码与分析

1. std::forward 源码总览

  std::forward() 的定义位于 /usr/include/c++/8/bits/move.h 中,详细内容如下:
std::forward
  可以看到在源码中,函数 std::forward 有两种实现,差别在于传参的类型,前者接收的传参是一个左值,而后者接收的传参是一个右值。另外在传右值的版本中存在一个静态断言,这也算是两者的区别之一吧。

2. std::forward 分析

  还是先代入不同的类型来化简 std::forward 函数。
  当模板类型 _Tpstring& 时,即 string 的左值引用时,std::forward 可以化简为如下形式:

    //string& && 引用折叠为 string&
    //std::remove_reference 提取出的类型为string
    string&
    forward(string& __t)
    { return static_cast<string&>(__t); }

    string&
    forward(string&& __t)
    {
      //此处静态断言忽略
      return static_cast<string&>(__t);
    }

  而当模板类型 _Tpstring&& 时,即 string 的右值引用时,std::forward 可以化简为如下形式:

    //string&& && 引用折叠为 string&&
    //std::remove_reference 提取出的类型为string
    string&&
    forward(string& __t)
    { return static_cast<string&&>(__t); }

    string&&
    forward(string&& __t)
    {
      //此处静态断言忽略
      return static_cast<string&&>(__t);
    }

  可以看到,当传入类型为左值引用时,返回值为 string& : return static_cast<string&>(__t);;当传入类型为右值引用时,返回值为 string&& : return static_cast<string&&>(__t);。这就实现了 std::forward 的功能,即完美转发。简单点来讲就是传入左值返回还是左值,传入右值返回还是右值,保持原来的值属性不变。std::forward 就相当于一个转发点,可以将类型原封不动的转发走。

三、完美转发

  上文中说到 std::forward 的功能,即完美转发。那么,何时需要它的存在呢?

通过下面这个例子我觉得可以很好的说明:
测试
执行结果如下:
结果

  可以看到,当传参为左值时,直接转发和完美转发的结果都是左值,是正确的。但是当传参是右值时,直接转发和完美转发的结果却不同了。
  分析其原因,当一个右值作为传参被传入后,函数内便会分配栈空间来保存传参。此时的右值已经被传参中的变量名所指向,所以再次传入此右值时,实际传入的是指向它的变量名,即一个左值。所以会出现上图中传参为右值但是直接转发却为左值的情况。
  std::forward 完美转发的大致应用场景便是上面那种,当存在中间转发点时,如果想要保持原来类型,则可以使用 std::forward 来实现。比如写一个筛选站,符合条件放 vector1 里,不符合放 vector2 里,总所周知 vectorpush_back 右值的时候会调用 emplace_back,它可以直接拿走右值,提高效率。所以为了可以正确的把右值push进去,就得使用std::forward 来进行完美转发。

四、总结

  总的来说,std::forward 可以说是对模板、引用折叠特性的一个巧妙利用。它和 std::move 的思路上有很大的相似之处,实现上也可以说是很相似。
  之前说实话,对模板用的并不算太多,仅仅实现了一些小东西。但是目前管中窥豹,了解了自己对于模板方面的不足,之后应该会写一些东西来熟悉,比如说尽力按STL标准写个跳表之类的。总之在模板方面还是有很大的进步空间。

### 回答1: std::move和std::forward都是C++中的类型转换函数,但它们作用不同。std::move将一个左值转换为右值,以使之能够进行移动操作,从而避免产生不必要的复制行为,提高程序的性能;而std::forward则是转发函数模板,将原本传入的参数保持其属性,比如如果传入的是左值,则转发后仍为左值,传入的是右值,则转发后仍为右值。这两个函数在模板编程、移动操作、完美转发等场景中常常被使用。 ### 回答2: std::move和std::forwardC++11新加入的两个重要的语言特性,它们都是在实现移动语义时使用的,std::move用于强制将左值转换为右值引用,从而支持移动构造和移动赋值操作;std::forward则用于完美转发,在模板中传递参数的引用类型。本文将对这两个函数进行源码分析。 1. std::move源码分析 std::move的定义非常简单,只是将其参数强制转换为右值引用类型: template<typename T> typename std::remove_reference<T>::type&& move(T&& t) noexcept { return static_cast<typename remove_reference<T>::type&&>(t); } 可以看到,它使用了std::remove_reference模板来去除T的引用。然后使用static_cast将t强制转换成右值引用类型,并返回。 当我们调用std::move时,实际上就是将一个左值转换为右值引用,以达到优化移动语义的目的。例如: std::vector<int> v1; std::vector<int> v2 = std::move(v1); //移动构造 在上面的代码中,我们将v1移动到v2中,这样就可以避免复制(通过重用已分配的内存)。注意,这只在T类型实现了移动构造函数的情况下才适用。 2. std::forward源码分析 std::forward作用是在模板中完美转发参数。它需要通过保留参数类型和值类别的方式,将参数转发给别的函数。它的定义如下: template <typename T> constexpr T&& forward(typename std::remove_reference<T>::type& t) noexcept { return static_cast<T&&>(t); } template <typename T> constexpr T&& forward(typename std::remove_reference<T>::type&& t) noexcept { static_assert(!std::is_lvalue_reference<T>::value, "template argument substituting T is an lvalue reference type"); return static_cast<T&&>(t); } 可以看到,std::forward是通过两个模板函数来实现的,一个是左值引用,一个是右值引用。前者需要保留参数的左值引用,而后者需要判断参数是否是左值引用类型,如果是,则编译时会给出错误。 在两个函数中,都使用了std::remove_reference模板来删除参数类型的引用,然后使用static_cast将参数转换成相应的引用类型。例如: void foo(std::string& str) { bar(std::forward<std::string>(str)); } 在这个例子中,str是函数参数的左值引用,我们希望将它转发给bar函数,保留其左值引用。如果我们使用std::move,则会将str的值类别转换成右值引用,这样可能会导致编译错误或实现不必要的复制。因此,我们应该使用std::forward完美转发str,并且保留其左值引用类型。 总结 std::move和std::forward是在C++11中加入的两个重要的语言特性,用于实现移动语义。std::move将左值转换为右值引用以支持移动构造和移动赋值操作,而std::forward则用于完美转发,在模板中传递参数的引用类型。它们的实现都依赖于std::remove_reference模板进行参数类型的去除引用操作,并使用static_cast将参数转换为相应的引用类型。学习和掌握这两个函数对我们理解现代C++编程有很大的帮助。 ### 回答3: std::move和std::forwardC++11标准中非常重要的两个函数模板,它们都可以用于C++中的移动语义,以提高程序的性能。在实际开发中,std::move和std::forward经常被用来优化参数传递和返回值语句,并且它们的使用方法与意义相似。 1. std::move std::move函数的定义如下所示: ```cpp template <typename T> typename remove_reference<T>::type&& move(T&& arg) noexcept; ``` 该函数的作用是将对象的左值转化为右值,从而进行移动操作。它接受一个模板参数T,然后将该参数转化为它对应的右值引用。如果T是左值类型,那么move函数返回该类型的一个右值引用。 在源码中,我们可以看到move函数实际上是将传入的参数arg强制转化为右值引用类型,并且使用了noexcept关键字,表示该函数是不抛出异常的。在移动语义中,对象的资源被移动而非复制,因此在move函数中使用右值引用类型可以避免无谓的复制操作,提高程序性能。 2. std::forward std::forward函数的定义如下所示: ```cpp template <typename T> T&& forward(typename remove_reference<T>::type& arg) noexcept; template <typename T> T&& forward(typename remove_reference<T>::type&& arg) noexcept; ``` 该函数的作用是针对完美转发进行优化,即按照形参的值类型来进行参数传递,以避免多次拷贝构造或移动构造,并且保留了右值特性。在语义上,它可以保证实参的值类型被完全保留,无论是左值还是右值。 在源码中,我们可以看到forward函数的模板参数为typename T,并且该函数定义了两个重载版本,分别用于左值和右值类型。当T为左值类型时,我们希望将实参arg作为左值类型进行传递,因此我们使用左值引用类型;当T为右值类型时,实参arg被认为是右值类型,因此我们使用右值引用类型。在使用forward进行参数传递时,根据函数模板参数的值类型自动匹配对应的重载版本,从而实现完美转发。 综上所述,std::move和std::forwardC++11中非常重要的函数模板,它们可以有效地提高程序性能,减少不必要的复制操作,并且针对完美转发进行了优化,具有非常广泛的应用场景。在实际开发中,我们需要根据语义需求来选择合适的函数进行参数传递和返回值处理,以达到优化程序性能的目的。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值