右值系列之六:向前,向前!

原文來自:http://cpp-next.com/archive/2009/12/onward-forward/

除了提供轉移語義,右值引用的另一個主要用途是解決“完美轉發”。在這裏,“轉發”的指將一個泛型函數的實參轉發至另一個函數而不會拒絕掉第二個參數可接受的任何參數,也不會丟失關於這些參數的cv限定或左右值屬性的任何信息,而且還無須採用重載。在C++03中,最佳的近似是將所有右值變爲左值,並且需要兩個重載。

爲什麼要解決這個問題?

考慮以下例子:

  1. template <class F>  
  2. struct unary_function_wrapper  
  3. {  
  4.      unary_function_wrapper(F f) : f(f) {}  
  5.    
  6.      template <class ArgumentType>  
  7.      void operator()( ArgumentType x ) { f(x); }  
  8.  private:  
  9.      F f;  
  10. };  

這種方式是不行的,因爲我們的函數調用操作符是傳值的,它會拒絕所有不可複製/不可轉移的類型,即便 f 可以接受這些類型。如果我們把它改爲:

  1. template <class ArgumentType>  
  2. void operator()( ArgumentType& x ) { f(x); }  

那麼我們會拒絕所有非常量性的右值。我們可以加一個重載:

  1. template <class ArgumentType>  
  2. void operator()( ArgumentType& x ) { f(x); }  
  3. template <class ArgumentType>  
  4. void operator()( ArgumentType const& x ) { f(x); }  

但是這也和前面的一樣,會丟失右值性,而我們本想保留它的,以便 f 可以利用那些我們在前面幾篇文章中討論過的轉移優化。引入第二個重載還會帶來另一個問題:它不能擴展至多個參數的情形。一個 binary_function_wrapper 就需要四個重載,再來一個 ternary_function_wrapper 就需要八個重載了,完美轉發 n 個參數需要 2^n 個重載。

可行的解決方案

對於右值引用,我們可以利用一些特殊設計的語言規則來解決這個問題:

  1. template <class ArgumentType>  
  2. void operator()( ArgumentType && x )  
  3. { f( std::forward<ArgumentType>(x) ); }  

這兩個特殊規則是:

  1. 關於右值引用消除的規則。在C++0x中,很早以前就決定了如果 T 爲 U&,則 T& 也爲 U&。這是左值引用消除。而對於右值引用,規則被更改爲:
    1. & + & 變爲 &
    2. & + && 變爲 &
    3. && + & 變爲 &
    4. && + && 變爲 &&

    即,任一”左值性”都會令結果變爲左值。

  2. 關於推定“全泛化右值引用”參數(如上例中的 ArgumentType)的規則。該規則規定,如果實參是一個右值,則 ArgumentType 被推定爲非引用類型,而如果實參是一個左值,則 ArgumentType 則被推定爲左值引用類型。

當實參爲類型 Y 的右值時,這兩個規則的結果是:ArgumentType 被推定爲 Y,因此這裏只有一個引用且沒有被消除:函數的參數類型爲 Y&&。

當實參是類型 Y 的左值時,ArgumentType 被推定爲 Y& (或 Y const&),且引用消除規則會起作用,使得實例化的函數的參數類型爲 Y& && 即 Y&...它剛好被綁定到一個左值。

最後一個要點是 forward 函數,它的任務是,當 ArgumentType 爲非引用時,“重建”實參的右值性,從而無干擾地把它傳遞給 f。

和 std::move 一樣,std::forward 是一個零開銷的操作。雖然它不是一個真正的轉換,但是你可以把 std::forward<ArgumentType>(x) 想象爲 static_cast<ArgumentType&&>(x) 的描述性表示:當實參是一個右值時,我們把 x 轉型爲一個匿名右值引用,但是當實參是一個左值時,ArgumentType 就是一個左值引用且引用消除會起作用,因此 static_cast 的結果仍是一個左值引用。

“forward” 實際上意味着什麼?

最近,關於 forward 的定義是否應該進行調整以適用於除“完美轉發”以外的用途,產生了一些實質性的異議,這些其它用途包括:用於類似於 std::tuple 這樣的類型的轉移構造函數,這些類型可能含用引用成員,並且要防止將左值引用綁定到右值成員這樣的危險情形。被建議的調整通常被稱爲幫助你“把一個 X 當作一個 Y 來轉發”。有關這些調整的細節,請參考 N2951

我從來沒有認同過這個方向的調整,因爲“轉發一個函數的實參並保持它們的cv限定及右值性”對我來說非常有意義,而“把 X 當作 Y 來轉發”則沒有明顯的意義。換句話說,這種 forward 如何使用以及爲何要用,並沒有明顯的心理模型:我們沒有與之對應的編程模型。我最終放棄對此的抵抗,這是因爲我看到,它在某些用戶必須要處理的與類相關的一些問題方面是有用的,但是我仍然認爲,我們必須指出它的意義所在,並且把它解釋清楚。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章