Cpp / 通用引用、引用折叠与完美转发问题
一、通用引用
通用引用,允許其綁定右值(就像右值引用那樣)和左值(就像左值引用那樣)。而且,它們可以綁定 const 或者非 const 對象,可以綁定 volatile 和非 volatile 對象,還可以綁定 const 和 volatile 同時作用的對象。它們實際上可以綁定任何東西。構成通用引用有兩個條件:
-
必須精確滿足 T&& 這種形式(即使加上 const 也不行)
-
類型T必須是通過推斷得到的(最常見的就是模板函數參數)
二、引用折疊
引用折疊規則:
- X& &,X& &&,X&& & 折疊為:X&
- X&& && 折疊為:X&&
三、std::move 的工作原理
由以上知識,就可以解釋一下 std::move 的工作原理了:
template <typename T> typename remove_reference<T>::type &&move(T &&t) {return static_cast<typename remove_reference<T>::type &&>(t); }首先,move 函數的參數類型是通用引用 T&&,可以綁定任意類型參數,其次,返回值是 remove_reference<T> 的 type 成員類型的右值引用,比如 T 被推導為 int 或者 int&,則remove_reference<int&>::type 為 int 類型,返回值類型為 int&&,最后,函數體中 static_cast 內的轉換過程類似,雖然不能隱式的將一個左值轉換為一個右值引用,但是通過 static_cast 顯式轉換時允許的(把左值截斷問題縮小在使用std::move代碼的范圍內)。
四、完美轉發
有時候,某些函數需要將其實參連同類型(const、左值、右值等屬性)不變的轉發給其他函數。
template <typename F, typename T> void sender(F receiver, T t) //sender函數,接受一個可調用對象和一個模板參數類型的參數 {receiver(t); //sender需要將自己的參數t轉發給receiver函數 }一般情況下這個函數能工作,但是當它調用一個接受引用類型參數的函數時就會有問題:
void rec(int &i) { ++i; } int j = 1; rec(j); cout << j << endl; //輸出 j 為 2;//但是通過 sender 調用時: template <typename F, typename T> void sender(F receiver, T t) {receiver(t); } sender(rec, j); cout << j << endl; //輸出 j 為 1 。其原因是 j 傳遞給 sender 函數,推斷出 T 為 int 類型而非引用,j 的值是被拷貝到形參 t 中的,因此對 t 值的改變不會反應到 j 中。
這時聯想到我們講的通用引用,將模板參數類型定義為 T&&,接受左值時,T 會被推斷為左值引用類型,經過一次引用折疊,得參數 t 的類型為左值引用,它對應實參的 const 屬性和左值、右值屬性都將得到保持:
template <typename F, typename T> void sender(F receiver, T &&t) //通用引用 {receiver(t); }void rec(int &i) {++i; } sender(rec, j); cout << j << endl; //OK!輸出 j 為 2 。但是這兒又會遇到另一個問題:
template <typename F, typename T> void sender(F receiver, T &&t) {receiver(t); }void rec(int &&i) //現在rec接受一個右值引用參數 {++i; }int j = 1; sender(rec, j); //錯誤:無法從一個左值實例化int&& sender(rec, 1); //錯誤:無法從一個左值實例化int&&當我們試圖對一個接受右值引用的函數轉發參數時,會報以上錯誤,不論我們傳遞給 sender 函數的是一個左值還是右值。原因是傳遞給 rec 函數中形參 i 的是 sender 函數中的參數 t,函數參數和其他變量一樣都是左值表達式!所以會出現將左值綁定到右值引用的錯誤。
這時需要用到 forward 函數來保證:當 sender 函數接受一個右值實參,轉發給 rec 函數時仍然能保持其右值屬性。forward 函數定義在 <utility> 頭文件中。
//forward函數 //lvalue (1) template <typename Type> Type &&forward(typename remove_reference<Type>::type &arg) noexcept {return (static_cast<Type &&>(arg)); } //rvalue (2) template <typename Type> Type &&forward(typename remove_reference<Type>::type &&arg) noexcept {return (static_cast<Type &&>(arg)); }forward 函數的工作原理:由 arg 接受的實參類型推斷出 Type 類型。
forward 函數必須通過顯式模板實參來調用,它跟通用引用配合可以保存原始實參的所有特性,回到我們的例子:
當傳遞給 sender 的是一個右值——比如 10 時,推斷出來的 T 是一個普通類型即 int,傳給 forward 的形參 arg 的實參就是一個 int,從而推出 Type 是 int,這時調用 std::forward<int>(t) 返回的是 int&&,保存了原實參的右值屬性。
當傳遞給 sender 的是一個左值——比如 j 時,這時我們推斷出來的 T 應該是一個 int& 了,調用 std::forward<int&>(t) 返回 int & &&(無效代碼,演示)折疊成為 int&,保存了原實參的左值屬性。
?
轉載于:https://blog.csdn.net/qq_38216239/article/details/80815142
?
(SAW:Game Over!)
總結
以上是生活随笔為你收集整理的Cpp / 通用引用、引用折叠与完美转发问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Cpp / std::move 原理
- 下一篇: OS / 进程启动过程