C++11——轉移和完美轉發
1. move
在 C++11 添加了右值引用,並且不能使用左值初始化右值引用,如果想要使用左值初始化一個右值引用需要藉助 std::move () 函式,
使用std::move方法可以將左值轉換為右值。使用這個函式並不能移動任何東西,而是和移動建構函式一樣都具有移動語義,將物件的狀態或者所有權從一個物件轉移到另一個物件,只是轉移,沒有記憶體拷貝。
從實現上講,std::move 基本等同於一個型別轉換:static_cast<T&&>(lvalue);,函式原型如下:
template<class _Ty> _NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) _NOEXCEPT { // forward _Arg as movable return (static_cast<remove_reference_t<_Ty>&&>(_Arg)); }
使用方法如下:
class Test { public: Test() {} ...... } int main() { Test t; Test&& v1 = t; // error Test&& v2 = move(t); //ok return 0; }
在第 4 行中,使用左值初始化右值引用,因此語法是錯誤的
在第 5 行中,使用 move() 函式將左值轉換為了右值,這樣就可以初始化右值引用了。
假設一個臨時容器很大,並且需要將這個容器賦值給另一個容器,就可以執行如下操作:
list<string> ls; ls.push_back("hello"); ls.push_back("world"); ...... list<string> ls1 = ls; // 需要拷貝, 效率低 list<string> ls2 = move(ls);
如果不使用 std::move,拷貝的代價很大,效能較低。使用 move 幾乎沒有任何代價,只是轉換了資源的所有權
2. forward
右值引用型別是獨立於值的,一個右值引用作為函式引數的形參時,在函式內部轉發該引數給內部其他函式時,它就變成一個左值,並不是原來的型別了。
如果需要按照參數原來的型別轉發到另一個函式,可以使用 C++11 提供的 std::forward () 函式,該函式實現的功能稱之為完美轉發。
// 函式原型 template <class T> T&& forward (typename remove_reference<T>::type& t) noexcept; template <class T> T&& forward (typename remove_reference<T>::type&& t) noexcept; // 精簡之後的樣子 std::forward<T>(t); 當T為左值引用型別時,t將被轉換為T型別的左值 當T不是左值引用型別時,t將被轉換為T型別的右值
下面通過一個例子演示一下關於 forward 的使用:
#include <iostream> using namespace std; template<typename T> void printValue(T& t) { cout << "l-value: " << t << endl; } template<typename T> void printValue(T&& t) { cout << "r-value: " << t << endl; } template<typename T> void testForward(T&& v) { printValue(v); printValue(move(v)); printValue(forward<T>(v)); cout << endl; } int main() { testForward(520); int num = 1314; testForward(num); testForward(forward<int>(num)); testForward(forward<int&>(num)); testForward(forward<int&&>(num)); return 0; }
測試程式碼列印的結果如下:
l-value: 520 r-value: 520 r-value: 520 l-value: 1314 r-value: 1314 l-value: 1314 l-value: 1314 r-value: 1314 r-value: 1314 l-value: 1314 r-value: 1314 l-value: 1314 l-value: 1314 r-value: 1314 r-value: 1314
testForward(520); 函式的形參為未定引用型別 T&&,實參為右值,初始化後被推導為一個右值引用
printValue(v); 已命名的右值 v,編譯器會視為左值處理,實參為左值
printValue(move(v)); 已命名的右值編譯器會視為左值處理,通過 move 又將其轉換為右值,實參為右值
printValue(forward<T>(v));forward 的模板引數為右值引用,最終得到一個右值,實參為 ``右值`
testForward(num); 函數的形參為未定引用型別 T&&,實參為左值,初始化後被推導為一個左值引用
printValue(v); 實參為左值
printValue(move(v)); 通過 move 將左值轉換為右值,實參為右值
printValue(forward<T>(v));forward 的模板引數為左值引用,最終得到一個左值引用,實參為左值
testForward(forward<int>(num));forward 的模板型別為 int,最終會得到一個右值,函式的形參為未定引用型別 T&& 被右值初始化後得到一個右值引用型別
printValue(v); 已命名的右值 v,編譯器會視為左值處理,實參為左值
printValue(move(v)); 已命名的右值編譯器會視為左值處理,通過 move 又將其轉換為右值,實參為右值
printValue(forward<T>(v));forward 的模板引數為右值引用,最終得到一個右值,實參為右值
testForward(forward<int&>(num));forward 的模板型別為 int&,最終會得到一個左值,函式的形參為未定引用型別 T&& 被左值初始化後得到一個左值引用型別
printValue(v); 實參為左值
printValue(move(v)); 通過 move 將左值轉換為右值,實參為右值
printValue(forward<T>(v));forward 的模板引數為左值引用,最終得到一個左值,實參為左值
testForward(forward<int&&>(num));forward 的模板型別為 int&&,最終會得到一個右值,函式的形參為未定引用型別 T&& 被右值初始化後得到一個右值引用型別
printValue(v); 已命名的右值 v,編譯器會視為左值處理,實參為左值
printValue(move(v)); 已命名的右值編譯器會視為左值處理,通過 move 又將其轉換為右值,實參為右值
printValue(forward<T>(v));forward 的模板引數為右值引用,最終得到一個右值,實參為右值