引入 C++11引入了大量新特性,包括值类型的细分,以及右值引用,移动语义等等。包括std::move。
移动语义 移动语义,顾名思义,意为资源从一个对象移动到另一个对象,即资源所有权的转移,以避免拷贝时的性能开销,直接进行转移而不是复制。
设想有一个对象的生命周期即将结束(多为纯右值,亡值情况),而我们恰好又需要这部分资源,我们就可以调用移动构造,或移动赋值,将资源转移走,而不是赋值。
移动构造器 class A {public : explicit A (int val) : resource(new int { val}) {} A (const A& other) { resource = new int {*other.resource}; } A (A&& other) noexcept { resource = other.resource; other.resource = nullptr ; } ~A () { if (resource) { delete resource; resource = nullptr ; } } private : int * resource = nullptr ; };
移动赋值 class A {public : explicit A (int val) : resource(new int { val}) {} A& operator =(A& other) { if (this != &other) { delete resource; resource = new int {*other.resource}; } return *this ; } A& operator =(A&& other) noexcept { delete resource; resource = other.resource; other.resource = nullptr ; return *this ; } ~A () { if (resource) { delete resource; resource = nullptr ; } } private : int * resource = nullptr ; };
移动构造/赋值使用场景 A retA (int num) { return A (num*num); } int main () { A move_con1 (retA(5 )) ; A move_con2 (A(6 )) ; A move_assign1 (7 ) ; move_assign1 = retA (8 ); A move_assign2 (9 ) ; move_assign2 = A (10 ); }
std::move使用场景 以上场景都是针对纯右值/亡值而调用移动构造/赋值的情况,针对左值时,我们无法使用移动语义,所以引入了std::move,它可以将左值引用强制转换为右值引用以匹配移动语义。
源码剖析 两者都万变不离其宗,首先需要调用std::remove_reference<_Tp>::type
用于移除_Tp
的引用部分,然后再添加右值引用符号&&
。这样,函数的返回类型就是一个右值引用。然后return static_cast<typename std::remove_reference<_Tp>::type&&>(__t);
,它使用static_cast
将参数__t
转换为一个右值引用,并返回转换后的结果。其中move(_Tp&& __t)
中_Tp&&
为万能引用。
GCC 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 ); }
MSVC _EXPORT_STD template <class _Ty > _NODISCARD _MSVC_INTRINSIC constexpr remove_reference_t <_Ty>&& move (_Ty&& _Arg) noexcept { return static_cast <remove_reference_t <_Ty>&&>(_Arg); }
示例 void swapAAssign (A& a, A& b) { A temp = a; a = b; b = temp; } void swapAMove (A& a, A& b) { A temp (std::move(a)) ; a = std::move (b); b = std::move (temp); } int main () { A a (5 ) , b (6 ) ; for (int i = 0 ; i < 1000000 ; ++i) { swapAAssign (a, b); } for (int i = 0 ; i < 1000000 ; ++i) { swapAMove (a, b); } }
对swapAAssign
与swapAMove
分别调用1000000 次并记录运行时间:
swapAAssign
: 543985 microseconds
swapAMove
: 9966 microseconds
读者可自行验证