C++11完美轉發

C++11 完美轉發

簡介

該篇博客主要介紹C++11中的完美轉發,對完美轉發的原理和使用方法進行介紹。

完美轉發

完美轉發的定義

所謂完美轉發(perfect forwarding),是指在函數模板中,完全依照模板的參數的類型,將參數傳遞給函數模板中調用的另外一個函數。比如:

template <typename T> 
void IamForwarding(T t) {  IrunCodeActually(t); }

在上面的例子中,IamForwarding的參數中使用了最基本類型進行轉發,該方法會導致參數在傳給IrunCodeActually之前就產生了一次額外的臨時對象拷貝。這樣的轉發只能說是正確的轉發,但談不上完美,我們希望的是IamForwarding能夠將參數按照傳入時的類型傳遞給IrunCodeActually

使用“萬能”引用

再看下面的代碼,嘗試使用“萬能”的常量左值類型,但是常量左值爲參數的轉發函數會有一些尷尬:

void IrunCodeActually(int i) { }
template <typename T> 
void IamForwarding(const T& t) {  IrunCodeActually(t); }

大家可以明顯看出其中的缺陷,由於目標函數的參數類型是非常量左值引用類型,因此無法接收常量左值引用作爲參數。雖然使用“萬能”引用提高了轉發函數的接受能力,但在目標函數的接受上卻出了問題。雖然可以通過一些常量和非常量的重載來解決目標函數的接受問題,但若函數參數比較多的情況下,就會造成代碼冗餘。並且若目標函數的參數是個右值引用,同樣無法接受任何左值類型作爲參數,也就無法使用移動語義。

引用摺疊規則

在C++11以前,形如下列語句:

typedef const int T;
typedef T& TR;
TR& v = 1;             // 該聲明在C++98中會導致編譯錯誤

其中TR& v = 1這樣的表達式會被編譯器認爲是不合法的表達式,而在C++11中,一旦出現了這樣的表達式,就會發生引用摺疊。C++11中的引用摺疊規則如下表所示:

TR的類型定義 聲明v的類型 v的實際類型
T& TR T&
T& TR& T&
T& TR&& T&
T&& TR T&&
T&& TR& T&
T&& TR&& T&&

爲利用上述摺疊規則,我們將轉發函數寫成如下形式:

template <typename T>
void IamForwarding(T&& t) {
	IrunCodeActually(static_cast<T &&>(t));
}

上述轉發函數可以實現完美轉發,但當傳入左值引用時,會覺得static_cast多餘,但在此處,它就是用於傳入一個右值引用時發揮作用。因爲當傳入一個右值時,當使用右值引用表達式引用的時候,該右值引用卻是一個不折不扣的左值,那麼我們要想在函數調用中繼續傳遞右值,就需要使用std::move來進行左右值的轉換。而std::move通常就是一個static_cast。不過在C++11中,用於完美轉發的函數卻不再叫作move,而是forward。所以我們可以將上述轉發函數寫成這樣:

template <typename T>
void IamForwarding(T&& t) {
	IrunCodeActually(std::forward<T>(t));
}

總結引用摺疊規則如下:

  1. 所有的右值引用疊加到右值引用上仍然使一個右值引用;
  2. 所有的其他引用類型之間的疊加都將變成左值引用。

完美轉發用例

#include <iostream>
using namespace std;

void RunCode(int && m) { cout << "rvalue ref" << endl; }
void RunCode(int & m) { cout << "lvalue ref" << endl; }
void RunCode(const int && m) { cout << "const rvalue ref" << endl; }
void RunCode(const int & m) { cout << "const lvalue ref" << endl; }

template <typename T>
void PerfectForward(T &&t) {   RunCode(forward<T>(t));    }

int main() {
    int a;
    int b;
    const int c = 1;
    const int d = 0;

    PerfectForward(a);          // lvalue ref
    PerfectForward(move(b));    // rvalue ref
    PerfectForward(c);          // const lvalue ref
    PerfectForward(move(d));    // const rvalue ref
}

注意:只有當發生自動類型推斷時(如函數模板的類型自動推導,或auto關鍵字),&&纔是一個universal references

關鍵字forward

從完美轉發的例子可以看出要實現完美轉發需要std::forward的配合。
下面是C++11std::forward的實現源碼。它一共有兩個版本,一個是處理左值,一個處理右值。
在參數中通過std::remove_reference解除引用。在函數體中使用static_cast

 /**
   *  @brief  Forward an lvalue.
   *  @return The parameter cast to the specified type.
   *
   *  This function is used to implement "perfect forwarding".
   */
  template<typename _Tp>
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type& __t) noexcept
    { return static_cast<_Tp&&>(__t); }

  /**
   *  @brief  Forward an rvalue.
   *  @return The parameter cast to the specified type.
   *
   *  This function is used to implement "perfect forwarding".
   */
  template<typename _Tp>
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
    {
      static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
		    " substituting _Tp is an lvalue reference type");
      return static_cast<_Tp&&>(__t);
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章