C++复习笔记--完美转发、std::move和std::forward的使用

目录

1--完美转发

1-1--实例代码分析

2--std::forward的使用

2-1--std::forward的源码

2-2--std::forward大白话总结

3--std::move的使用

3-1--std::move的源码

3-2--std::move大白话总结


1--完美转发

为什么需要完美转发

C++ Primer 关于转发 (P612 ) 有这么一段话:某些函数需要将其一个或多个实参连同类型不变 地转发给其他函数,在此情况下,我们需要保持被转发实参的所有性质

用大白话来讲,就是希望转发某些参数时,我们不希望这个参数在转发过程中被改变了(例如由右值改变为了左值),而完美转发就可以实现上面的愿景;

1-1--实例代码分析

结合以下代码讲解实现完美转发的实例:代码参考

cpp 复制代码
#include <iostream>

template<typename T>
void print(T & t){ // 只接受左值
    std::cout << "Lvalue ref" << std::endl;
}

template<typename T>
void print(T && t){ // 万能引用,可以接受左值和右值
    std::cout << "Rvalue ref" << std::endl;
}

template<typename T>
void testForward(T && v){ 
    print(v);
    print(std::forward<T>(v)); 
    print(std::move(v)); 

    std::cout << "======================" << std::endl;
}

int main(int argc, char * argv[]){

    int x = 1;
    testForward(x); // 传递左值
    testForward(1); // 传递右值
}

代码运行结果如下:

cpp 复制代码
Lvalue ref
Lvalue ref
Rvalue ref
======================
Lvalue ref
Rvalue ref
Rvalue ref
======================

代码分析

对于第一个 testForward(x) 的执行:

① x 本身是一个左值,当传递给函数 testForward(T && v) 时,T 会被推断为 int &推断分析参考笔记:万能引用和引用折叠);函数模板等价于 testForward(int & v) ;(发生了引用折叠 T && → int & && → int & );执行第一个 print(v) 时,v是一个左值 ,因此优先调用第一个 print() 函数;

② 执行第二个 print(std::forward<T>(v)) 时,v 是一个左值,经过 std::forward<T>(v) 返回的值 仍然是一个左值 (后面会结合源码分析 std::forward 的使用),因此优先调用第一个 print() 函数;

③ 执行第三个 print(std::move(v)) 时,v 是一个左值,经过 std::move(v) 返回的值 是一个右值 (后面会结合源码分析 std::move 的使用),因此只能调用第二个 print() 函数;

对于第二个 testForward(1) 的执行:

① 字面值 1 本身是一个右值 ,当传递给函数 testForward(T && v) 时,T 会被推断为 int ;函数模板等价于 testForward(int v) ; 执行第一个 print(v) 时,由于 v 这时用一个变量拥有地址 )来存储了数值 1,因此 v 是一个左值 ,会优先调用第一个 print() 函数;

② 执行第二个 print(std::forward<T>(v)) 时,v 是一个左值,由于 T 被推断为 int ,则经过 std::forward<T>(v) 返回的值 是一个右值 (会结合源码分析),因此只能调用第二个 print() 函数;

③ 执行第三个 print(std::move(v)) 时,v 是一个左值,经过 std::move(v) 返回的值 是一个右值 (会结合源码分析),因此只能调用第二个 print() 函数;

上面实例是为了说明 std::forward<T>() 函数能够根据 T 的不同,返回值的同时,能够很好地维持本来该有的类型属性 ;以下会结合源码分析 std::forward 和 std::move 的作用;

2--std::forward的使用

2-1--std::forward的源码

cpp 复制代码
/**
   *  @brief  Forward an lvalue.
   *  @return The parameter cast to the specified type.
   *
   *  This function is used to implement "perfect forwarding".
   */
  template<typename _Tp>
    _GLIBCXX_NODISCARD
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type& __t) noexcept
    { return static_cast<_Tp&&>(__t); }

  /**
   *  @brief  Forward an rvalue.
   *  @return The parameter cast to the specified type.
   *
   *  This function is used to implement "perfect forwarding".
   */
  template<typename _Tp>
    _GLIBCXX_NODISCARD
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
    {
      static_assert(!std::is_lvalue_reference<_Tp>::value,
	  "std::forward must not be used to convert an rvalue to an lvalue");
      return static_cast<_Tp&&>(__t);
    }

简单结合第一个模板的源码来分析上面的代码实例:

① 对于 std::forward<T>(v),当 v 是一个左值 ,且 T 已经被推断为 int & 时的情况,std::forward<T>(v) 等价于 std::forward<int&>(v);即对于以下的源码,传入的 _Tp 是 int&

cpp 复制代码
  template<typename _Tp>
    _GLIBCXX_NODISCARD
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type& __t) noexcept
    { return static_cast<_Tp&&>(__t); }

对于上面的源码,首先需知道 std::remove_reference<int&>::type 的结果是int (具体可见C++ Primer Page 605),_Tp&&发生引用折叠_Tp&& → int& && → int& ,则源码等价于 constexpr int& forward(int& __t)返回的类型int& ,即返回的类型对应左值引用 ,则返回的值 是一个左值

② 对于 std::forward<T>(v),当 v 是一个左值 ,且 T 已经被推断为 int 时的情况,

std::forward<T>(v) 等价于 std::forward<int>(v);即对于上面的源码,传入的 _Tp 是 int ;std::remove_reference<int>::type 的结果仍然是int ,则源码等价于 constexpr int&& forward(int& __t)返回的类型int&& ,即返回的类型对应右值引用 ,则返回的值 是一个右值

(这里可能会有疑惑?明明返回的类型是右值引用,右值引用虽然只能绑定右值,但其本质上是一个左值呀,为什么返回的是右值呀?)(回答:首先返回类型和返回的值是不同的,std::forward这里返回的是值 ,这个值并没有用一个变量去存储,而是直接返回了,因此这个返回的值是右值 ,不能调用上面实例的第一个print()函数,尽管这个返回的值的类型是右值引用int &&)

cpp 复制代码
// 对于下面的代码, auto是被推断为 int&& 的,可以调用上面实例中的第一个print函数
// 因为这时用一个变量a来接收返回的右值,a就变成了左值,左值可以调用print()函数
auto a = std::forward<int>(v);
cpp 复制代码
#include <iostream>

template<typename T>
void print(T & t){ // 只接受左值
    std::cout << "Lvalue ref" << std::endl;
}

int main(int argc, char * argv[]){

    int v = 1;
    auto a = std::forward<int>(v); // int &&a = 1; 
    // a 变成了左值
    print(a);
}

2-2--std::forward大白话总结

当传入一个实参 到 std::forward 中,其显式类型是T ,则返回的值与实参的值相同 ,且返回类型T&&

3--std::move的使用

3-1--std::move的源码

cpp 复制代码
/**
   *  @brief  Convert a value to an rvalue.
   *  @param  __t  A thing of arbitrary type.
   *  @return The parameter cast to an rvalue-reference to allow moving it.
  */
  template<typename _Tp>
    _GLIBCXX_NODISCARD
    constexpr typename std::remove_reference<_Tp>::type&&
    move(_Tp&& __t) noexcept
    { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

简单结合模板的源码来分析上面的代码实例:

① 对于std::move(v) 时,v 是一个左值 ,v 的类型是 int & ,当传入 std::move(v) 函数时,_Tp推断为int & ,源码等价于 int&& move(int& _t); 接收一个左值,返回类型 是 int&&,即右值引用 类型,返回的值 是一个右值,因此只能调用上面代码实例中的第二个 print() 函数;

② 对于std::move(v) 时,v 是一个左值 ,v 的类型是 int ,当传入 std::move(v) 函数时,_Tp推断为int ,源码等价于 int&& move(int&& _t); 接收一个右值,返回类型 是 int&&,即右值引用 类型,返回的值 是一个右值,因此也只能调用上面代码实例中的第二个 print() 函数;

3-2--std::move大白话总结

std::move()无论传入的是一个左值还是一个右值,返回的都是一个右值 ,其类型是右值引用类型

相关推荐
2401_881244402 小时前
牛客周赛99
c++
山登绝顶我为峰 3(^v^)34 小时前
如何录制带备注的演示文稿(LaTex Beamer + Pympress)
c++·线性代数·算法·计算机·密码学·音视频·latex
十五年专注C++开发7 小时前
CMake基础:条件判断详解
c++·跨平台·cmake·自动化编译
QuantumStack9 小时前
【C++ 真题】P1104 生日
开发语言·c++·算法
天若有情67310 小时前
01_软件卓越之道:功能性与需求满足
c++·软件工程·软件
whoarethenext10 小时前
使用 C++/OpenCV 和 MFCC 构建双重认证智能门禁系统
开发语言·c++·opencv·mfcc
Jay_51511 小时前
C++多态与虚函数详解:从入门到精通
开发语言·c++
xiaolang_8616_wjl12 小时前
c++文字游戏_闯关打怪
开发语言·数据结构·c++·算法·c++20
FrostedLotus·霜莲12 小时前
C++主流编辑器特点比较
开发语言·c++·编辑器
liulilittle17 小时前
深度剖析:OPENPPP2 libtcpip 实现原理与架构设计
开发语言·网络·c++·tcp/ip·智能路由器·tcp·通信