簡體   English   中英

std::move 和 std::forward 有什么區別

[英]What's the difference between std::move and std::forward

我在這里看到了這個: 移動構造函數調用基類移動構造函數

有人可以解釋一下:

  1. std::movestd::forward之間的區別,最好是一些代碼示例?
  2. 如何輕松思考,何時使用 which

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::forwardstd::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::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_tT中剝離任何引用,然后添加&&來實現。

瑣事

你知道嗎,除了我們正在討論<utility>std::move之外,還有另一個? 是的,它是std::move from <algorithm> ,它做了一件幾乎不相關的事情:它是std::copy的一個版本,它不是值從一個容器復制到另一個容器,而是使用std::move from <utility> ; 所以它是一個使用另一個 std:: std::movestd::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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM