1. 程式人生 > >std::move 和 std::forward

std::move 和 std::forward

std::move

C++11引入了名為move的庫函式,該函式可以獲得繫結到左值上的右值引用,定義在標頭檔案<utility>中。

該函式的定義如下:

template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
    return static_cast<typename remove_reference<T>::type&&>(t);
}

由程式碼可以看出,move是一個模板函式,模板函式的模板引數由編譯器根據函式實參來進行推導,因此接下來我們從不同型別函式引數來推斷一下模板函式的例項化結果。

1、實參為左值

int i = 0;
std::move(i);
可知,傳遞給move的實參是一個左值,因此推斷出T的型別為 int&,模板函式例項化為
remove_reference<int&>::type&& move(int& && t)
{
    return static_cast<remove_reference<int&>::type&&>(t);
}
remove_reference<int&>::type的結果為去引用,即int。int & &&經過引用摺疊後為int &。因此最終結果為
int&& move(int& t)
{
    return static_cast<int&&>(t);
}
static_cast為顯示轉換函式,將引數t由int&型別轉換為int&&型別,最終返回int&&型別。

2、實參為右值

int &&j = 0;
std::move(j);
可知,傳遞給move的實參是一個右值,因此推斷出T的型別為int, 模板函式例項化為
remove_reference<int>::type&& move(int&& t)
{
    return static_cast<remove_reference<int>::type&&>(t);
}
remove_reference<int>::type的結果為int, 由於t為int&&型別,因此static_cast<int &&>(t)仍舊為int&&型別,因此最終函式返回int&&型別。

由上的分析可知,move函式返回的是右值引用。

要注意,我們約定既然使用了move函式,便認為傳入物件已經處於失效狀態,從而除了重新賦值使之生效效或者銷燬之外,不應該再對其進行操作。

std::forward

std::forward程式碼如下
template<class _Ty> inline
constexpr _Ty&& forward(typename remove_reference<_Ty>::type& _Arg) _NOEXCEPT
{	
	// forward an lvalue as either an lvalue or an rvalue
	return (static_cast<_Ty&&>(_Arg));
}

template<class _Ty> inline
constexpr _Ty&& forward(typename remove_reference<_Ty>::type&& _Arg) _NOEXCEPT
{	
	// forward an rvalue as an rvalue
	static_assert(!is_lvalue_reference<_Ty>::value, "bad forward call");
	return (static_cast<_Ty&&>(_Arg));
}
該模板函式的作用是實現完美轉發,具體看下面的示例。
template <typename Type> void testFor(Type &&arg)
{
    innerFunc(std::forward<Type>(arg));
}
若arg為左值引用,比如int&,則Type被推導為int&,arg為左值引用,適用於std::forward的第一個模板,_Ty為int&,從而返回值為int & &&,摺疊為int&。 若arg為右值引用,比如int&&, 則Type被推導為int,  arg為右值引用,適用於std::forward的第二個模板,_Ty為int,從而返回值為int &&。 即std::forward可以保留引數左值引用和右值引用的特性。 若不使用此type traits,在轉發過程中會存在問題。如:
void innerFunc1(int &a)
{
cout << a << endl;
}

void innerFunc2(int &&a)
{
cout << a << endl;
}

template <typename F, typename Type> void testFunc(F f, Type &&arg1)
{
	f(arg1);
}
可知該模板的目的是對F函式呼叫傳入testFunc2的右值引用引數,但是呼叫testFunc(innerFunc2, 10)會編譯錯誤,因為函式引數是左值表示式,因此F(arg1)會編譯失敗。 要解決此問題,我們可以修改testFunc函式的內容為如下來解決。
f(static_cast<typename remove_reference<Type>::type&&>(arg1));

這樣似乎對於右值引用能夠很好的解決,但是對於F為含有左值引用引數函式的情況就不適用了。 使用std::forward可以實現完美轉發。
template <typename F, typename Type> void testFunc(F f, Type &&arg1)
{
    f(std::forward<Type>(arg1));
}

這樣無論f為左值引數函式還是右值引數函式,均可以實現完美轉發。