【转载】C++11 forward完美转发

前言

继续阅读之前,你最好了解了左值,右值,左值引用,右值引用等概念。

最好阅读了C++11 move带来的高效

引入

这里我借上一篇C++11 move带来的高效中的CMyString类用一下,代码如下


   
   
     
     
     
     
  1. class CMyString
  2. {
  3. public:
  4. CMyString( char* pStr)
  5. : m_pStr( NULL)
  6. , m_nLen( 0)
  7. {
  8. if ( NULL != pStr)
  9. {
  10. m_nLen = strlen(pStr);
  11. m_pStr = new char[m_nLen + 1];
  12. memcpy(m_pStr, pStr, m_nLen);
  13. m_pStr[m_nLen] = 0;
  14. cout << "一般构造函数 str=" << m_pStr << endl;
  15. }
  16. }
  17. CMyString( const CMyString& o)
  18. : m_pStr( NULL)
  19. , m_nLen( 0)
  20. {
  21. if ( NULL != o.m_pStr)
  22. {
  23. m_nLen = o.m_nLen;
  24. m_pStr = new char[m_nLen + 1];
  25. memcpy(m_pStr, o.m_pStr, m_nLen);
  26. m_pStr[m_nLen] = 0;
  27. cout << "拷贝构造函数 str=" << m_pStr << endl;
  28. }
  29. }
  30. const CMyString& operator=(CMyString&& o)
  31. {
  32. char* pStrTmp = o.m_pStr;
  33. int nLen = o.m_nLen;
  34. o.m_pStr = m_pStr;
  35. o.m_nLen = m_nLen;
  36. m_pStr = pStrTmp;
  37. m_nLen = nLen;
  38. cout << "右值引用类型 重载赋值运算符 str=" << m_pStr << endl;
  39. return * this;
  40. }
  41. const CMyString& operator=( const CMyString& o)
  42. {
  43. if ( this != &o)
  44. {
  45. if ( NULL != m_pStr)
  46. {
  47. delete[] m_pStr;
  48. m_pStr = NULL;
  49. }
  50. m_nLen = o.m_nLen;
  51. if ( NULL != o.m_pStr)
  52. {
  53. m_pStr = new char[m_nLen + 1];
  54. memcpy(m_pStr, o.m_pStr, m_nLen);
  55. m_pStr[m_nLen] = 0;
  56. }
  57. cout << "重载赋值运算符 str=" << m_pStr << endl;
  58. }
  59. return * this;
  60. }
  61. ~CMyString()
  62. {
  63. if ( NULL != m_pStr)
  64. {
  65. //cout << "析构函数 str=" << m_pStr << endl;
  66. delete m_pStr;
  67. }
  68. }
  69. char* GetData()
  70. {
  71. return m_pStr;
  72. }
  73. CMyString(CMyString&& o)
  74. : m_pStr( NULL)
  75. , m_nLen( 0)
  76. {
  77. char* pStrTmp = o.m_pStr;
  78. int nLen = o.m_nLen;
  79. o.m_pStr = m_pStr;
  80. o.m_nLen = m_nLen;
  81. m_pStr = pStrTmp;
  82. m_nLen = nLen;
  83. cout << "右值引用类型 拷贝构造函数 str=" << m_pStr << endl;
  84. }
  85. void swap(CMyString& o)
  86. {
  87. char* pStrTmp = o.m_pStr;
  88. int nLen = o.m_nLen;
  89. o.m_pStr = m_pStr;
  90. o.m_nLen = m_nLen;
  91. m_pStr = pStrTmp;
  92. m_nLen = nLen;
  93. }
  94. private:
  95. char* m_pStr;
  96. int m_nLen;
  97. };
举一个栗子:我们内部有一个CoreFun函数需要一个CMyString对象,CoreFun函数我们不想暴露给客户,所以我们再封装一个ICoreFun来调用CoreFun,代码如下:


   
   
     
     
     
     
  1. void CoreFun(CMyString t)
  2. {
  3. cout << "CoreFun" << endl;
  4. }
  5. void ICoreFun(CMyString t)
  6. {
  7. cout << "ICoreFun" << endl;
  8. CoreFun(t);

很显然,ICoreFun函数只是起了一个转发的作用

测试一下:


   
   
     
     
     
     
  1. int _tmain( int argc, _TCHAR* argv[])
  2. {
  3. CMyString lvalue("hello this is the lvalue");
  4. ICoreFun(lvalue);
  5. system( "pause");
  6. }

执行效果如下:
【转载】C++11 forward完美转发_第1张图片

很显然,中间的一次拷贝构造函数是多余的,我们可以通过引用来优化掉:


   
   
     
     
     
     
  1. void ICoreFun(CMyString& t)
  2. {
  3. cout << "ICoreFun" << endl;
  4. CoreFun(t);
  5. }

【转载】C++11 forward完美转发_第2张图片

进阶

上面我们的ICoreFun函数的参数是一个左值引用,如果我需要传递一个右值进来的时候怎么办呢?例如ICoreFun(CMyString("hello this is the rvalue"));

(注:VS上自定义类对象右值也可以绑定到左值引用上)

难道要再写一个ICoreFun(CMyString&&)的接口吗?有没有什么办法

当我ICoreFun(lvalue);的时候ICoreFun的函数原型是ICoreFun(CMyString&)

当我ICoreFun(CMyString("hello this is the rvalue"));的时候ICoreFun的函数原型是ICoreFun(CMyString&&)

模板是一个很好的选择,如下的模板可以解决这个问题:


   
   
     
     
     
     
  1. template < typename T>
  2. void CoreFun(T t)
  3. {
  4. cout << "CoreFun" << endl;
  5. }
  6. template < typename T>
  7. void ICoreFun(T&& t)
  8. {
  9. cout << "ICoreFun" << endl;
  10. CoreFun(t);
  11. }
想要理解如上模板的函数中T&&如何实现我们上面的需求的,需要知道C11引入的 “引用折叠规则” ( reference collapsing rules ),有如下两条:

对右值引用的两个规则中的第一个也同样影响了旧式的左值引用。回想在pre-11 C++时,对一个引用取引用是是不被允许的,比如A& &会导致一个编译器错误。相比之下,在C++11中,引入了如下所示的引用折叠规则(reference collapsing rules):

1. A& &变成A&
2. A& &&变成A&
3. A&& &变成A&
4. A&& &&变成A&&
第二条,有一个对应于函数模板中模板参数的特殊的演绎规则,当其模板参数为右值引用类型的时候:

template
void foo(T&&);
下面,这些规则会生效:

当foo被一个类型为A的左值调用时,T会被转化为A&,因此根据上面的引用折叠规则,这个参数类型实际上会变成A&。
当foo被一个类型为A的右值调用是,T会被转化成A,因此这个参数类型实际上会变成A&&。

现在我们根据上面的规则来推到一下,T&&是怎么实现

当我ICoreFun(lvalue);的时候ICoreFun的函数原型是ICoreFun(CMyString&)

当我ICoreFun(CMyString("hello this is the rvalue"));的时候ICoreFun的函数原型是ICoreFun(CMyString&&)的:

【转载】C++11 forward完美转发_第3张图片

运行程序看一下效果:


   
   
     
     
     
     
  1. int _tmain( int argc, _TCHAR* argv[])
  2. {
  3. CMyString lvalue("hello this is the lvalue");
  4. ICoreFun(lvalue);
  5. cout << endl;
  6. ICoreFun(CMyString( "hello this is the rvalue"));
  7. system( "pause");
  8. }
【转载】C++11 forward完美转发_第4张图片

这里可以思考了,ICoreFun参数是右值的时候,拷贝构造函数是可以用 右值类型的拷贝构造函数优化的,回顾上一篇move带来的高效中,我们掌握了一种优化方式:对于使用临时对象来构造另一个对象的时候,完全可以通过右值类型的拷贝构造函数中的高效交换来实现,而不是调用拷贝构造函数重新进行new delete操作,因为临时对象马上就要销毁了,所以我们可以只是使用指针的交换来实现了构造的效果。

但是虽然ICoreFun(CMyString("hello this is the rvalue")); 转换为了ICoreFun(CMyString&& t),但是,t是一个左值,所以CoreFun(t);的时候调用的是拷贝构造函数来构造t,

t是一个左值都能理解吧,就和

int&& r = 8;

8是一个右值,r的类型是右值引用,r本身是一个左值,是一个道理的

全都是因为增加了一层转发,CMyString("hello this is the rvalue")从右值变为了左值

forward

有没有办法,在上面的情况中,在CMyString&& t 传递给CoreFun(t)的时候,CoreFun(t)把t当作左值来处理呢?也就在t是CMyString&&类型的时候把t转换为右值,在t是CMyString& t类型的时候不转换。forward就是这个作用。

forward作用:获取参数的原有类型(不太形象,具体看总结中的定义去理解下)

代码修改如下:


   
   
     
     
     
     
  1. template < typename T>
  2. void CoreFun(T t)
  3. {
  4. cout << "CoreFun" << endl;
  5. }
  6. template < typename T>
  7. void ICoreFun(T&& t)
  8. {
  9. cout << "ICoreFun" << endl;
  10. CoreFun( std::forward(t));
  11. }
运行结果:

【转载】C++11 forward完美转发_第5张图片
转发左值就调用拷贝构造函数,抓发右值就调用右值类型的拷贝构造函数,完美。

总结

std::forward argument: Returns an rvalue reference to arg if arg is not an lvalue reference; If arg is an lvalue reference, the function returns arg without modifying its type.

std::forward:This is a helper function to allow perfect forwarding of arguments taken as rvalue references to deduced types, preserving any potential move semantics involved. 

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

std::move是无条件的转为右值引用,而std::forward是有条件的转为右值引用,更准确的说叫做Perfect forwarding(完美转发),而std::forward里面蕴含着的条件则是Reference Collapsing(引用折叠)。

std::move不move任何东西。std::forward也不转发任何东西。在运行时,他们什么都不做。不产生可执行代码,一个比特的代码也不产生。

std::move和std::forward只是执行转换的函数(确切的说应该是函数模板)。std::move无条件的将它的参数转换成一个右值,而std::forward当特定的条件满足时,才会执行它的转换。

std::move表现为无条件的右值转换,就其本身而已,它不会移动任何东西。 std::forward仅当参数被右值绑定时,才会把参数转换为右值。 std::move和std::forward在运行时不做任何事情。



你可能感兴趣的:(C++)