![](/img/trans.png)
[英]What is the difference between std::forward<T>() and std::move when perfectly forwarding rvalue references?
[英]What's the difference between std::move and std::forward
std::move
接受一個對象並允許您將其視為臨時對象(右值)。 盡管這不是語義要求,但通常接受對右值的引用的函數會使它無效。 當您看到std::move
時,它表明該對象的值以后不應使用,但您仍然可以分配一個新值並繼續使用它。
std::forward
有一個用例:將模板函數參數(在函數內部)轉換為調用者用來傳遞它的值類別(左值或右值)。 這允許將右值參數作為右值傳遞,並將左值作為左值傳遞,這種方案稱為“完美轉發”。
為了說明:
void overloaded( int const &arg ) { std::cout << "by lvalue\n"; }
void overloaded( int && arg ) { std::cout << "by rvalue\n"; }
template< typename t >
/* "t &&" with "t" being template param is special, and adjusts "t" to be
(for example) "int &" or non-ref "int" so std::forward knows what to do. */
void forwarding( t && arg ) {
std::cout << "via std::forward: ";
overloaded( std::forward< t >( arg ) );
std::cout << "via std::move: ";
overloaded( std::move( arg ) ); // conceptually this would invalidate arg
std::cout << "by simple passing: ";
overloaded( arg );
}
int main() {
std::cout << "initial caller passes rvalue:\n";
forwarding( 5 );
std::cout << "initial caller passes lvalue:\n";
int x = 5;
forwarding( x );
}
正如霍華德所提到的,這兩個函數也有相似之處,因為這兩個函數都只是轉換為引用類型。 但在這些特定用例(涵蓋 99.9% 的右值引用強制轉換的有用性)之外,您應該直接使用static_cast
並為您正在做的事情寫一個很好的解釋。
std::forward
和std::move
都只是強制轉換。
X x;
std::move(x);
上面將 X 類型的左值表達式x
轉換為 X 類型的右值表達式(確切地說是一個 xvalue)。 move
也可以接受一個右值:
std::move(make_X());
在這種情況下,它是一個恆等函數:接受 X 類型的右值並返回 X 類型的右值。
使用std::forward
您可以在一定程度上選擇目的地:
X x;
std::forward<Y>(x);
將 X 類型的左值表達式x
轉換為 Y 類型的表達式。 Y 可以是什么是有限制的。
Y 可以是 X 的可訪問 Base,或對 X 的 Base 的引用。Y 可以是 X,或對 X 的引用。不能用forward
拋棄 cv 限定符,但可以添加 cv 限定符。 Y 不能是只能從 X 轉換的類型,除非通過可訪問的 Base 轉換。
如果 Y 是左值引用,則結果將是左值表達式。 如果 Y 不是左值引用,則結果將是一個右值(准確地說是 x 值)表達式。
僅當 Y 不是左值引用時, forward
才能接受右值參數。 也就是說,您不能將右值轉換為左值。 這是出於安全原因,因為這樣做通常會導致引用懸空。 但是將右值轉換為右值是可以的並且是允許的。
如果您嘗試將 Y 指定為不允許的內容,則會在編譯時而不是運行時捕獲錯誤。
std::forward
用於完全按照傳遞給函數的方式轉發參數。 就像這里顯示的:
使用std::move
提供一個對象作為右值,以匹配移動構造函數或接受右值的函數。 即使x
本身不是右值,它也會為std::move(x)
執行此操作。
我認為比較兩個示例實現可以提供很多關於它們的用途以及它們之間的區別的見解。
我將從std::move
開始,早在理解std::forward
之前我就真正理解了這一點。
std::move
長話短說: std::move
用於將任何東西變成右值(¹),目的是讓它看起來像一個臨時的(即使它不是: std::move(non_temporary)
),以便它的資源可以從中竊取,即從其中移出(前提是const
屬性沒有阻止這一點;是的,右值可以是const
,在這種情況下,您不能從它們那里竊取資源)。
std::move(x)
說嗨,伙計們,請注意,我給這個x
的人可以根據自己的喜好使用和分解它,所以你通常在右值引用參數上使用它,因為你確定它們是綁定的給臨時工。
這是std::move
的 C++14 實現,與 Scott Meyers 在Effective Modern C++中展示的非常相似(在書中,返回類型std::remove_reference_t<T>&&
更改為decltype(auto)
,由此推斷出它從return
聲明)
template<typename T>
std::remove_reference_t<T>&& move(T&& t) {
return static_cast<std::remove_reference_t<T>&&>(t);
}
從這里我們可以觀察到關於std::move
的以下內容:
T
;T&&
獲取其唯一參數,因此它可以對左值和右值進行操作; T
將相應地推導出為左值引用或非引用類型;<…>
指定模板參數,並且在實踐中,您永遠不應該指定它;std::move
只不過是一個static_cast
,其模板參數根據非模板參數自動確定,其類型是推導出來的;std::remove_reference_t
從T
中剝離任何引用,然后添加&&
來實現。你知道嗎,除了我們正在討論的<utility>
的std::move
之外,還有另一個? 是的,它是std::move
from <algorithm>
,它做了一件幾乎不相關的事情:它是std::copy
的一個版本,它不是將值從一個容器復制到另一個容器,而是使用std::move
from <utility>
; 所以它是一個使用另一個 std:: std::move
的std::move
。
std::forward
長話短說: std::forward
用於將參數從函數內部轉發到另一個函數,同時告訴后者函數是否使用臨時函數調用前者。
std::forward<X>(x)
說兩件事之一:
x
綁定到一個右值,即一個臨時值)您好,函數先生,我從另一個函數收到了這個包裹,您使用它后不需要它,所以請隨意對它做任何您喜歡的事情;x
綁定到一個左值,即非臨時性)您好,函數先生,我從另一個函數收到了這個包裹,您使用它后確實需要它,所以請不要破壞它。所以你通常在轉發/通用引用上使用它,因為它們可以綁定到臨時和非臨時。
換句話說, std::forward
是為了能夠打開這段代碼
template<typename T>
void wrapper(T&& /* univ. ref.: it binds to lvalues as well as rvalues (temporaries)*/ t) {
// here `t` is an lvalue, so it doesn't know whether it is bound to a temporary;
// `T` encodes this missing info, but sadly we're not making `some_func` aware of it,
// therefore `some_func` will not be able to steal resources from `t` if `t`
// is bound to a temporary, because it has to leave lvalues intact
some_func(t);
}
進入這個
template<typename T>
void wrapper(T&& /* univ. ref.: it binds to lvalues as well as rvalues (temporaries)*/ t) {
// here `t` is an lvalue, so it doesn't know whether it is bound to a temporary;
// `T` encodes this missing info, and we do use it:
// `t` bound to lvalue => `T` is lvalue ref => `std::forward` forwards `t` as lvalue
// `t` bound to rvalue => `T` is non-ref => `std::forward` turns `t` into rvalue
some_func(std::forward<T>(t));
}
這是同一本書中std::forward
的 C++14 實現:
template<typename T>
T&& forward(std::remove_reference_t<T>& t) {
return static_cast<T&&>(t);
}
由此我們可以觀察到關於std::forward
的以下內容:
T
;T
的左值引用獲取其唯一參數; 請注意,由於引用折疊(參見此處), std::remove_reference_t<T>&
解析為與T&
& 解析的完全相同的內容; 然而...std::remove_reference_t<T>&
而不是T&
的原因正是為了將T
置於非推導上下文中(參見此處),從而禁用模板類型推導,以便您被迫指定模板通過<…>
論證std::forward
只不過是一個static_cast
,模板參數根據您必須傳遞給std::forward
的模板參數自動確定(通過引用折疊);T&&
的引用折疊來做到這一點,其中T
是您作為模板參數傳遞給std::forward
的那個:如果T
是非引用,則T&&
是右值引用,而如果T
是左值引用,那么T&&
也是一個左值引用。¹有效現代 C++中的 Scott Meyers 准確地說如下:
std::move
無條件地將其參數轉換為右值
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.