C++中的萬能引用和完美轉發


本文轉發至:C++中的萬能引用和完美轉發

  1. 閱讀這篇博文需要了解C++中的左值(lvalue)和右值(rvalue)的概念,詳情參見我的另外一篇博文:C++移動語義及拷貝優化
  2. 萬能引用和完美轉發多涉及到模板的使用,如若不是自己寫模板,則可不用關心

萬能引用(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));
}

則可以輸出預期的結果。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM