本文轉發至:C++中的萬能引用和完美轉發
- 閱讀這篇博文需要了解C++中的左值(lvalue)和右值(rvalue)的概念,詳情參見我的另外一篇博文:C++移動語義及拷貝優化
- 萬能引用和完美轉發多涉及到模板的使用,如若不是自己寫模板,則可不用關心
萬能引用(Universal Reference)
首先,我們來看一個例子:
#include <iostream>
using std::cout;
using std::endl;
template<typename T>
void func(T& param) {
cout << param << endl;
}
int main() {
int num = 2019;
func(num);
return 0;
}
這樣例子的編譯輸出都沒有什么問題,但是如果我們修改成下面的調用方式呢?
int main() {
func(2019);
return 0;
}
則會得到一個大大的編譯錯誤。因為上面的模板函數只能接受左值或者左值引用(左值一般是有名字的變量,可以取到地址的),我們當然可以重載一個接受右值的模板函數,如下也可以達到效果。
template<typename T>
void func(T& param) {
cout << "傳入的是左值" << endl;
}
template<typename T>
void func(T&& param) {
cout << "傳入的是右值" << endl;
}
int main() {
int num = 2019;
func(num);
func(2019);
return 0;
}
輸出結果:
傳入的是左值傳入的是右值
第一次函數調用的是左值得版本,第二次函數調用的是右值版本。但是,有沒有辦法只寫一個模板函數即可以接收左值又可以接收右值呢?
C++ 11中有萬能引用(Universal Reference)的概念:使用T&&
類型的形參既能綁定右值,又能綁定左值。
但是注意了:只有發生類型推導的時候,T&&才表示萬能引用;否則,表示右值引用。
所以,上面的案例我們可以修改為:
template<typename T>
void func(T&& param) {
cout << param << endl;
}
int main() {
int num = 2019;
func(num);
func(2019);
return 0;
}
引用折疊(Universal Collapse)
萬能引用說完了,接着來聊引用折疊(Univers Collapse),因為完美轉發(Perfect Forwarding)的概念涉及引用折疊。一個模板函數,根據定義的形參和傳入的實參的類型,我們可以有下面四中組合:
- 左值-左值 T& & # 函數定義的形參類型是左值引用,傳入的實參是左值引用
- 左值-右值 T& && # 函數定義的形參類型是左值引用,傳入的實參是右值引用
- 右值-左值 T&& & # 函數定義的形參類型是右值引用,傳入的實參是左值引用
- 右值-右值 T&& && # 函數定義的形參類型是右值引用,傳入的實參是右值引用
但是C++中不允許對引用再進行引用,對於上述情況的處理有如下的規則:
所有的折疊引用最終都代表一個引用,要么是左值引用,要么是右值引用。規則是:如果任一引用為左值引用,則結果為左值引用。否則(即兩個都是右值引用),結果為右值引用。
即就是前面三種情況代表的都是左值引用,而第四種代表的右值引用。
完美轉發(Perfect Forwarding)
下面接着說完美轉發(Perfect Forwarding),首先,看一個例子:
#include <iostream>
using std::cout;
using std::endl;
template<typename T>
void func(T& param) {
cout << "傳入的是左值" << endl;
}
template<typename T>
void func(T&& param) {
cout << "傳入的是右值" << endl;
}
template<typename T>
void warp(T&& param) {
func(param);
}
int main() {
int num = 2019;
warp(num);
warp(2019);
return 0;
}
猜一下,上面的輸出結果是什么?
傳入的是左值傳入的是左值
是不是和我們預期的不一樣,下面我們來分析一下原因:
warp()
函數本身的形參是一個萬能引用,即可以接受左值又可以接受右值;第一個warp()
函數調用實參是左值,所以,warp()
函數中調用func()
中傳入的參數也應該是左值;第二個warp()
函數調用實參是右值,根據上面所說的引用折疊規則,warp()函數接收的參數類型是右值引用,那么為什么卻調用了調用
func()的左值版本了呢?這是因為在warp()
函數內部,左值引用類型變為了右值,因為參數有了名稱,我們也通過變量名取得變量地址。
那么問題來了,怎么保持函數調用過程中,變量類型的不變呢?這就是我們所謂的“完美轉發”技術,在C++11中通過std::forward()
函數來實現。我們修改我們的warp()
函數如下:
template<typename T>
void warp(T&& param) {
func(std::forward<T>(param));
}
則可以輸出預期的結果。