move和forward源码分析[转]

来源:互联网 发布:linux mount sd卡 编辑:程序博客网 时间:2024/06/08 00:26

原文:C++11尝鲜:std::move和std::forward源码分析

std::movestd::forward是C++0x中新增的标准库函数,分别用于实现移动语义和完美转发。

下面让我们分析一下这两个函数在gcc4.6中的具体实现。

预备知识

引用折叠规则

X&  + &  => X&X&& + &  => X&X&  + && => X&X&& + && => X&&

函数模板参数推导规则(右值引用参数部分)

当函数模板的模板参数为T而函数形参为T&&(右值引用)时适用本规则。

  • 若实参为左值U&,则模板参数T应推导为引用类型U&。根据引用折叠规则,U& + && => U&,而T&& ≡ U&,故T ≡ U&
  • 若实参为右值U&&,则模板参数T应推导为非引用类型U。根据引用折叠规则,U或U&& + && => U&&,而T&& ≡ U&&,故T ≡ U或U&&,这里强制规定T ≡ U

std::remove_reference

std::remove_reference为C++0x标准库中的元函数,其功能为去除类型中的引用。

std::remove_reference<U&>::type ≡ Ustd::remove_reference<U&&>::type ≡ Ustd::remove_reference<U>::type ≡ U

static_cast

以下语法形式将把表达式t转换为T类型的右值(准确的说是无名右值引用,是右值的一种)

static_cast<T&&>(t)

无名的右值引用是右值,具名的右值引用是左值。

std::move

函数功能

std::move(t)负责将表达式t转换为右值,使用这一转换意味着你不再关心t的内容,它可以通过被移动(窃取)来解决移动语意问题。

源码与测试代码

001| template<typename _Tp>002|   inline typename std::remove_reference<_Tp>::type&&003|   move(_Tp&& __t)004|   { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
001| #include<iostream>002| using namespace std;003|004| struct X {};005| int main() {006|     X a;007|     X&& b = move(a);008|     X&& c = move(X());009| }

代码说明

  1. 测试代码第7行用X类型的左值a来测试move函数,根据标准X类型的右值引用b只能绑定X类型的右值,所以move(a)的返回值必然是X类型的右值。
  2. 测试代码第8行用X类型的右值X()来测试move函数,根据标准X类型的右值引用c只能绑定X类型的右值,所以move(X())的返回值必然是X类型的右值。
  3. 首先我们来分析move(a)这种用左值参数来调用move函数的情况。
  4. 模拟单步调用来到源码第3行,_Tp&& ≡ X&, __t ≡ a
  5. 根据函数模板参数推导规则,_Tp&& ≡ X&可推出_Tp ≡ X&
  6. typename std::remove_reference<_Tp>::type ≡ X
    typename std::remove_reference<_Tp>::type&& ≡ X&&
  7. 再次单步调用进入move函数实体所在的源码第4行。
  8. static_cast<typename std::remove_reference<_Tp>::type&&>(__t) ≡ static_cast<X&&>(a)
  9. 根据标准static_cast<X&&>(a)将把左值a转换为X类型的无名右值引用。
  10. 然后我们再来分析move(X())这种用右值参数来调用move函数的情况。
  11. 模拟单步调用来到源码第3行_Tp&& ≡ X&&, __t ≡ X()
  12. 根据函数模板参数推导规则,_Tp&& ≡ X&&可推出_Tp ≡ X
  13. typename std::remove_reference<_Tp>::type ≡ X
    typename std::remove_reference<_Tp>::type&& ≡ X&&
  14. 再次单步调用进入move函数实体所在的源码第4行。
  15. static_cast<typename std::remove_reference<_Tp>::type&&>(__t) ≡ static_cast<X&&>(X())
  16. 根据标准static_cast<X&&>(X())将把右值X()转换为X类型的无名右值引用。

由9和16可知源码中std::move函数的具体实现符合标准,因为无论用左值a还是右值X()做参数来调用std::move函数,该实现都将返回无名的右值引用(右值的一种),符合标准中该函数的定义。

std::forward

函数功能

std::forward<T>(u)有两个参数:Tu。当T为左值引用类型时,u将被转换为T类型的左值,否则u将被转换为T类型右值。如此定义std::forward是为了在使用右值引用参数的函数模板中解决参数的完美转发问题。

源码与测试代码

001| /// forward (as per N3143)002| template<typename _Tp>003|   inline _Tp&&004|   forward(typename std::remove_reference<_Tp>::type& __t)005|   { return static_cast<_Tp&&>(__t); }006|007| template<typename _Tp>008|   inline _Tp&&009|   forward(typename std::remove_reference<_Tp>::type&& __t)010|   {011|     static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"012|     " substituting _Tp is an lvalue reference type");013|     return static_cast<_Tp&&>(__t);014|   }
001| #include<iostream>002| using namespace std;003|004| struct X {};005| void inner(const X&) {cout << "inner(const X&)" << endl;}006| void inner(X&&) {cout << "inner(X&&)" << endl;}007| template<typename T>008| void outer(T&& t) {inner(forward<T>(t));}009|010| int main()011| {012|     X a;013|     outer(a);014|     outer(X());015|     inner(forward<X>(X()));016| }

运行结果:

inner(const X&)inner(X&&)inner(X&&)

代码说明

  1. 测试代码第13行用X类型的左值a来测试forward函数,程序输出表明outer(a)调用的是inner(const X&)版本,从而证明函数模板outer调用forward函数在将参数左值a转发给了inner函数时,成功地保留了参数a的左值属性。
  2. 测试代码第14行用X类型的右值X()来测试forward函数,程序输出表明outer(X())调用的是inner(X&&)版本,从而证明函数模板outer调用forward函数在将参数右值X()转发给了inner函数时,成功地保留了参数X()的右值属性。
  3. 首先我们来分析outer(a)这种调用forward函数转发左值参数的情况。
  4. 模拟单步调用来到测试代码第8行,T&& ≡ X&, t ≡ a
  5. 根据函数模板参数推导规则,T&& ≡ X&可推出T ≡ X&
  6. forward<T>(t) ≡ forward<X&>(t),其中t为指向a的左值引用。
  7. 再次单步调用进入forward函数实体所在的源码第4行或第9行。
  8. 先尝试匹配源码第4行的forward函数,_Tp ≡ X&
  9. typename std::remove_reference<_Tp>::type ≡ X
    typename std::remove_reference<_Tp>::type& ≡ X&
  10. 形参__t与实参t类型相同,因此函数匹配成功。
  11. 再尝试匹配源码第9行的forward函数,_Tp ≡ X&
  12. typename std::remove_reference<_Tp>::type ≡ X
    typename std::remove_reference<_Tp>::type&& ≡ X&&
  13. 形参__t与实参t类型不同,因此函数匹配失败。
  14. 由10与13可知7单步调用实际进入的是源码第4行的forward函数。
  15. static_cast<_Tp&&>(__t) ≡ static_cast<X&>(t) ≡ a
  16. inner(forward<T>(t)) ≡ inner(static_cast<X&>(t)) ≡ inner(a)
  17. outer(a) ≡ inner(forward<T>(t)) ≡ inner(a),再次单步调用将进入测试代码第5行的inner(const X&)版本,左值参数转发成功。
  18. 然后我们来分析outer(X())这种调用forward函数转发右值参数的情况。
  19. 模拟单步调用来到测试代码第8行,T&& ≡ X&&, t ≡ X()
  20. 根据函数模板参数推导规则,T&& ≡ X&&可推出T ≡ X
  21. forward<T>(t) ≡ forward<X>(t),其中t为指向X()的右值引用。
  22. 再次单步调用进入forward函数实体所在的源码第4行或第9行。
  23. 先尝试匹配源码第4行的forward函数,_Tp ≡ X
  24. typename std::remove_reference<_Tp>::type ≡ X
    typename std::remove_reference<_Tp>::type& ≡ X&
  25. 形参__t与实参t类型相同,因此函数匹配成功。
  26. 再尝试匹配源码第9行的forward函数,_Tp ≡ X
  27. typename std::remove_reference<_Tp>::type ≡ X
    typename std::remove_reference<_Tp>::type&& ≡ X&&
  28. 形参__t与实参t类型不同,因此函数匹配失败。
  29. 由25与28可知22单步调用实际进入的仍然是源码第4行的forward函数
  30. static_cast<_Tp&&>(__t) ≡ static_cast<X&&>(t) ≡ X()
  31. inner(forward<T>(t)) ≡ inner(static_cast<X&&>(t)) ≡ inner(X())
  32. outer(X()) ≡ inner(forward<T>(t)) ≡ inner(X()),再次单步调用将进入测试代码第6行的inner(X&&)版本,右值参数转发成功。

由17和32可知,源码中std::forward函数的具体实现符合标准,因为无论用左值a还是右值X()做参数来调用带有右值引用参数的函数模板outer,只要在outer函数内使用std::forward函数转发参数,就能保留参数的左右值属性,从而实现了函数模板参数的完美转发。

原创粉丝点击