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