move
和forward
是cpp11
引入的两个模板函数, move
配合移动语义对应的函数(移动构造, 移动赋值)可以减少不必要的拷贝, 而forward
可以完美的保留参数的特性, 从而实现预期的行为.
引用 从cpp11
以来, 引用可以分为左值引用
、右值引用
和通用引用
. 左值引用
和右值引用
分别可以绑定到左值
和右值
上, 从而在函数调用过程中减少不必要的拷贝. 通用引用
用于泛型编程中, 其可以绑定到左值上, 也可以绑定到右值上.
引用折叠 cpp
规定不存在指向引用的引用. 在泛型编程中, 如果模板函数指定参数类型为T&& param
, 而传入的参数可以是左值也可以是右值, 因此T&&
作为通用引用
就需要利用引用折叠规则进行参数类型的适配.
如果参数param
为左值或左值引用, 那么T
就被推导为T&
, 这样参数类型就变成了T& &¶m
, 引用折叠规则规定这种情况下T
为左值引用.
如果参数param
为右值, 那么T
就被推导为T
, 这样参数类型就变成了T&& param
.
如果参数param
为命名右值引用, 那么T
被推导为T&
, 因为命名右值引用为左值, T &&
无法绑定到左值上去, 因此推导为T&
.
如果参数param
为匿名右值引用, 那么T
被推导为T
, 因为匿名右值引用为右值.
Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <iostream> using namespace std;template <typename T>void testType (T&& param) { if (is_rvalue_reference<decltype (param)>::value) cout<<"param is rvalue reference\n" ; else cout<<"param is lvalue reference\n" ; } int main () { int val = 10 ; int & L = val; int && R = 123 ; testType (val); testType (L); testType (R); testType (static_cast <int &&>(val)); testType (10086 ); return 0 ; }
Result 1 2 3 4 5 param is lvalue reference param is lvalue reference param is lvalue reference param is rvalue reference param is rvalue reference
move move
函数无条件的将传入参数转换为右值引用返回. 由于函数返回右值引用为匿名右值引用, 因此其可以配合移动构造函数
和移动赋值函数
实现移动语义.
move源码 1 2 3 4 5 6 7 8 9 10 11 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 ); }
move
函数的源码很简单. 函数的参数类型为通用引用
, 返回值类型为constexpr typename std::remove_reference<_Tp>::type&&
, 其中std::remove_reference<_Tp>::type
是偏特化模板, 类似于traits
, 提取的是_Tp
的去掉引用后的类型(如int&为int, char&&为char). 函数的主体为一条类型转化的语句: static_cast<typename std::remove_reference<_Tp>::type&&>(__t)
. 其意很明确: 将参数转换为对应类型的右值引用并返回.
使用样例 move
函数的过程很简单, 就是简单的类型转换. 其可以认为是一个小的语法糖, 主要配合移动构造函数
和移动赋值函数
实现移动语义.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 #include <iostream> #include <string> using namespace std;class Array {public : Array () : _size(0 ), _ptr(nullptr ) {} Array (int len) { _size = len; _ptr = new int [_size]; } Array (Array& a) { _size = a._size; _ptr = new int [_size]; for (int i = 0 ; i < _size; i ++ ) _ptr[i] = a._ptr[i]; cout << "called copy-cotr. " << endl; } Array& operator = (Array& a) { if (this == &a) return *this ; if (_ptr != nullptr ) delete _ptr; _size = a._size; _ptr = new int [_size]; for (int i = 0 ; i < _size; i ++ ) _ptr[i] = a._ptr[i]; cout << "called copy operator = " << endl; return *this ; } Array (Array&& a) { _size = a._size; _ptr = a._ptr; a._size = 0 ; a._ptr = nullptr ; cout << "called move cotr." << endl; } Array& operator = (Array&& a) { if (this == &a) return *this ; if (_ptr != nullptr ) delete _ptr; _size = a._size; _ptr = a._ptr; a._size = 0 ; a._ptr = nullptr ; cout << "called move operator = " << endl; return *this ; } ~ Array () { delete _ptr; cout << "called de-cotr." << endl; } void printInfo () { cout << "size of array is " << _size << endl; } private : int _size; int * _ptr; }; int main () { Array arr (10 ) ; Array dst; dst = move (arr); dst.printInfo (); arr.printInfo (); return 0 ; }
总结
move
函数无法移动任何东西, 其只是无条件的返回参数的匿名右值引用.
move
需要配合移动构造函数
和移动赋值函数
才能正确实现移动语义
.
forward 源码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 template <typename _Tp>_GLIBCXX_NODISCARD constexpr _Tp&&forward (typename std::remove_reference<_Tp>::type& __t ) noexcept { return static_cast <_Tp&&>(__t ); }template <typename _Tp>_GLIBCXX_NODISCARD constexpr _Tp&&forward (typename std::remove_reference<_Tp>::type&& __t ) noexcept { static_assert (!std::is_lvalue_reference<_Tp>::value, "template argument" " substituting _Tp must not be an lvalue reference type" ); return static_cast <_Tp&&>(__t ); }
forward
可以有区别转发左值和右值, 其主要特点是通过函数重载实现不同参数类型的分别处理. 如果__t
是左值那么进入第一个版本, 如果__t
是右值那么进入第二个版本. 通过源码可以发现, 只有_Tp
为左值引用时, 参数__t
才会被转发成_Tp
的左值引用. 否则就是_Tp
类型的右值引用.
那么为什么需要forward
函数呢? 如果我们希望根据参数引用的类型实现不同的功能, 或者在函数调用中保留原始参数的引用特性, 就必须使用forward
函数进行完美转发.
使用样例 Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 #include <iostream> #include <string> using namespace std;class Array {public : Array () : _size(0 ), _ptr(nullptr ) {} Array (int len) { _size = len; _ptr = new int [_size]; } Array (Array& a) { _size = a._size; _ptr = new int [_size]; for (int i = 0 ; i < _size; i ++ ) _ptr[i] = a._ptr[i]; cout << "called copy-cotr. " << endl; } Array& operator = (Array& a) { if (this == &a) return *this ; if (_ptr != nullptr ) delete _ptr; _size = a._size; _ptr = new int [_size]; for (int i = 0 ; i < _size; i ++ ) _ptr[i] = a._ptr[i]; cout << "called copy operator = " << endl; return *this ; } Array (Array&& a) { _size = a._size; _ptr = a._ptr; a._size = 0 ; a._ptr = nullptr ; cout << "called move cotr." << endl; } Array& operator = (Array&& a) { if (this == &a) return *this ; if (_ptr != nullptr ) delete _ptr; _size = a._size; _ptr = a._ptr; a._size = 0 ; a._ptr = nullptr ; cout << "called move operator = " << endl; return *this ; } ~ Array () { delete _ptr; cout << "called de-cotr." << endl; } void printInfo () { cout << "size of array is " << _size << endl; } private : int _size; int * _ptr; }; template <typename T> T wrap (T&& src) { return forward<T&&>(src); } void funcB (Array&& src) { } template <typename T> void funcA (T&& src) { funcB (forward<T&&>(src)); } int main () { Array arr (10 ) ; Array dst1 = wrap (arr); Array dst2 = wrap (move (arr)); dst1.printInfo (); dst2.printInfo (); funcA (move (dst2)); return 0 ; }
Result 1 2 3 4 5 6 7 called copy-cotr. called move cotr. size of array is 10 size of array is 10 called de-cotr. called de-cotr. called de-cotr.
我们在main
函数里调用完美转发包装的wrap
函数. 通过传入参数的不同实现调用不同的构造函数.
在函数调用中, 由于命名右值引用是左值, 因此其无法进行期望的函数调用, 需要使用forward
转发
forward
完美转发会保留参数的const
和引用特性, 降低出错的可能.
总结 由于命名右值引用是左值, 因此在函数中继续转发该参数会发生不符合预期的结果出现(调用左值引用版本的代码、编译出错等等), 而forward
可以完美转发参数的const
特性和引用
特性, 从而保证执行符合我们的预期, 降低出错的可能性.