簡介
該篇博客主要介紹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));
}
總結引用摺疊規則如下:
- 所有的右值引用疊加到右值引用上仍然使一個右值引用;
- 所有的其他引用類型之間的疊加都將變成左值引用。
完美轉發用例
#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++11
中std::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);
}