移动语义、完美转发
右值引用(rvalue reference)是 C++11 為了實(shí)現(xiàn)移動(dòng)語(yǔ)意(move semantic)和完美轉(zhuǎn)發(fā)(perfect forwarding)而提出來(lái)的。
右值引用,簡(jiǎn)單說(shuō)就是綁定在右值上的引用。右值的內(nèi)容可以直接移動(dòng)(move)給左值對(duì)象,而不需要進(jìn)行深拷貝(deep copy)。
在面向?qū)ο笾?#xff0c;有的類是可以拷貝的,例如車、房等他們的屬性是可以復(fù)制的,可以調(diào)用拷貝構(gòu)造函數(shù),有點(diǎn)類的對(duì)象則是獨(dú)一無(wú)二的,或者類的資源是獨(dú)一無(wú)二的,比如 IO 、 std::unique_ptr等,他們不可以復(fù)制,但是可以把資源交出所有權(quán)給新的對(duì)象,稱為可以移動(dòng)的。
C++11最重要的一個(gè)改進(jìn)之一就是引入了移動(dòng)語(yǔ)義,這樣在一些對(duì)象的構(gòu)造時(shí)可以獲取到已有的資源(如內(nèi)存)而不需要通過(guò)拷貝,申請(qǐng)新的內(nèi)存,這樣移動(dòng)而非拷貝將會(huì)大幅度提升性能。例如有些右值即將消亡析構(gòu),這個(gè)時(shí)候我們用移動(dòng)構(gòu)造函數(shù)可以接管他們的資源。
C++11用std::move來(lái)實(shí)現(xiàn)移動(dòng)語(yǔ)義。下邊看一個(gè)小栗子,請(qǐng)忽略std::forward:
#include <iostream>#include <utility>void reference(int& v) { std::cout << "左值引用" << std::endl;}void reference(int&& v) { std::cout << "右值引用" << std::endl;}template <typename T>void pass(T&& v) { std::cout << "普通傳參:"; reference(v); std::cout << "std::move 傳參:"; reference(std::move(v)); std::cout << "std::forward 傳參:"; reference(std::forward<T>(v));}int main() { std::cout << "傳遞右值:" << std::endl; pass(1); std::cout?<<?"傳遞左值:"?<<?std::endl; int v = 1; pass(v); return?0;} 輸出: 傳遞右值:普通傳參:左值引用std::move 傳參:右值引用std::forward 傳參:右值引用傳遞左值:普通傳參:左值引用std::move 傳參:右值引用std::forward 傳參:左值引用可能有的小伙伴看出來(lái)異樣,
傳遞右值時(shí):
? pass函數(shù)明明傳入一個(gè)右值,但是打印出左值引用。
右值通過(guò)std::move就變成了右值引用。
傳遞左值時(shí):
??? 1. ?但是打印了左值引用。
????2.?左值引用通過(guò)std::move就變成了右值引用。
這兩個(gè)問(wèn)題2都好解釋,std::move函數(shù)就是為了移動(dòng)而生。
下邊說(shuō)一下傳遞右值的異常。
我們來(lái)看這個(gè)模板,編譯器將T推導(dǎo)為 int& 類型。當(dāng)我們用 int& 替換掉 T 后,得到 int & &&。看起來(lái)兩個(gè)&&被消除了。
但是我們嘗試寫下邊代碼的時(shí)候卻報(bào)錯(cuò)了!!!!
int & &rra = ra; // 編譯器報(bào)錯(cuò):不允許使用引用的引用!編譯器真是雙標(biāo)黨,編譯器不允許我們自己把代碼寫成int& &&,它自己卻這么干了 =。=?
那么 int & &&到底是個(gè)什么東西呢?!它就是引用折疊,也有人叫它引用坍縮,C++對(duì)于這種類型的折疊有下邊的規(guī)則:
| 函數(shù)形參類型 | 實(shí)參參數(shù)類型 | 推導(dǎo)后函數(shù)形參類型 |
| T& | 左引用 | T& |
| T& | 右引用 | T& |
| T&& | 左引用 | T& |
| T&& | 右引用 | T&& |
編譯器不允許我們寫下類似int & &&這樣的代碼,但是它自己卻可以推導(dǎo)出int & &&代碼出來(lái)。它的理由就是:我(編譯器)雖然推導(dǎo)出T為int&,但是我在最終生成的代碼中,利用引用折疊規(guī)則,將int & &&等價(jià)生成了int &。推導(dǎo)出來(lái)的int & &&只是過(guò)渡階段,最終版本并不存在。所以也不算破壞規(guī)定咯。
所有的引用折疊最終都代表一個(gè)引用,要么是左值引用,要么是右值引用。規(guī)則就是:如果任一引用為左值引用,則結(jié)果為左值引用。否則(即兩個(gè)都是右值引用),結(jié)果為右值引用。?????????????????????????????????????????????????????????????????????????《Effective?Modern?C++》
為了解決引用折疊引入的問(wèn)題,一個(gè)新的概念--完美轉(zhuǎn)發(fā)就閃亮登場(chǎng)了。
還是剛才的小程序,std::forward 傳參:?輸出的都是我們想要的類型。
std::forward的源碼形式大致是這樣:
/* * 精簡(jiǎn)了標(biāo)準(zhǔn)庫(kù)的代碼,在細(xì)節(jié)上可能不完全正確,但是足以讓我們了解轉(zhuǎn)發(fā)函數(shù) forward 的了 */ template<typename T>T&& forward(T ¶m){ return static_cast<T&&>(param);}我們來(lái)仔細(xì)分析一下這段代碼, 可以看到,不管T是值類型,還是左值引用,還是右值引用,T&經(jīng)過(guò)引用折疊,都將是左值引用類型。也就是forward 以左值引用的形式接收參數(shù) param, 然后 通過(guò)將param進(jìn)行強(qiáng)制類型轉(zhuǎn)換 static_cast<T&&> (),最終再以一個(gè) T&&返回,根據(jù)上邊的引用折疊規(guī)則,就能完美的forward傳參。
再進(jìn)一步:https://zhuanlan.zhihu.com/p/55229582
總結(jié)
- 上一篇: 左值、右值、左值引用、右值引用
- 下一篇: lambda 和 std::functi