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