右值引用与移动语义
目錄
一、左、右值引用
1.1 什么是左值
1.2 什么是右值
1.3 右值引用特性
1.4 move語義
二、左、右值引用的比較
三、右值引用的使用場景
3.1 左值引用的短板
3.2 解決方案?
四、移動構造與移動賦值
注意情況
五、萬能引用與完美轉發
5.1 萬能引用
5.2 完美轉發
一、左、右值引用
傳統的C++語法中就有引用的語法,而C++11中新增了的右值引用語法特性,所以我們稱之前學習的引用為左值引用。但無論左值引用還是右值引用,其實都是給對象取別名。
1.1 什么是左值
左值是一個表示數據的表達式(如變量名或解引用的指針),我們可以獲取它的地址,也可以對它賦
值。左值可以出現賦值符號的左邊,但右值不能出現在賦值符號左邊。定義時const修飾符后的左
值,不能給他賦值,但是可以取它的地址。左值引用就是給左值的引用,給左值取別名。(可以大致理解為,能夠取地址的一般都為左值)
1.2 什么是右值
右值也是一個表示數據的表達式,如:字面常量、表達式返回值,函數返回值(非左值引用返回)等等。右值可以出現在賦值符號的右邊,但是不能出現出現在賦值符號的左邊,右值不能取地址。右值引用就是對右值的引用,給右值取別名。
內置類型的右值引用被稱為純右值,自定義類型的右值引用被稱為將亡值
int main() {double x = 1.1, y = 2.2;//以下是常見的右值10;x + y;fmin(x, y);//以下是對右值的右值引用int&& rr1 = 10;double&& rr2 = x + y;double&& rr3 = fmin(x, y);return 0; }1.3 右值引用特性
右值是不能取地址的,但給右值取別名后,會導致右值被存儲到特定位置,且可以取到該位置的地址。(即可以給右值引用取地址)
因為右值引用是左值
1.4 move語義
按照語法,右值引用只能引用右值,但右值引用一定不能引用左值嗎?
有些場景下,可能真的需要用右值引用去引用左值實現移動語義。當需要用右值引用去引用一個左值時,可以通過move函數將左值轉化為右值。C++11中,std::move()函數位于<utility>頭文件中,該函數名字具有迷惑性,它并不搬移任何東西,唯一的功能就是將一個左值強制轉化為右值,然后實現移動語義。
template<class _Ty> inline typename remove_reference<_Ty>::type&& move(_Ty&& _Arg) _NOEXCEPT {// forward _Arg as movablereturn ((typename remove_reference<_Ty>::type&&)_Arg); } int main() {bjy::string s1("hello world");bjy::string s2(s1);//這里s1是左值,調用的是拷貝構造bjy::string s3(std::move(s1));//將s1 move處理以后,會被當成右值,調用移動構造//一般是不這樣用的,因為我們會發現s1的資源被轉移給了s3,s1被置空了。return 0; }二、左、右值引用的比較
左值引用:
1. 左值引用只能引用左值,不能引用右值
2. const左值引用既可以引用左值,也可以引用右值
int main() {//左值引用只能引用左值,不能引用右值。int a = 10;int& ra1 = a;//ra為a的別名//int& ra2 = 10;//編譯失敗,因為10是右值// const左值引用既可引用左值,也可引用右值。const int& ra3 = 10;const int& ra4 = a;return 0; }右值引用:
1.?右值引用只能引用右值,不能引用左值
2.?右值引用可以move以后的左值
int main() {// 右值引用只能右值,不能引用左值。int&& r1 = 10;int a = 10;int&& r2 = a;// error C2440: “初始化”: 無法從“int”轉換為“int &&”// message : 無法將左值綁定到右值引用// 右值引用可以引用move以后的左值int&& r3 = std::move(a);return 0; }三、右值引用的使用場景
const左值引用既可以引用左值,也可以引用右值,那么為什么還需要右值引用呢?
?左值引用看似功能已經很完善了,但是在面對下面這些情況時,卻捉襟見肘。
3.1 左值引用的短板
當函數返回對象是一個局部變量,出了函數作用域就不存在了,就不能使用左值引用返回,只能傳值返回。
3.2 解決方案?
這個時候右值引用便可以解決這個問題了。
// 拷貝構造 string(const string& s) :_str(nullptr), _size(0), _capacity(0) {cout << "string(const string& s) -- 拷貝構造(深拷貝)" << endl;string tmp(s._str);swap(tmp); } // 移動構造 string(string&& s) :_str(nullptr), _size(0), _capacity(0) {cout << "string(string&& s) -- 資源轉移" << endl;swap(s); }利用右值引用提供了移動構造函數后,to_string函數中的str對象會被編譯器識別為將亡值。之后需要發生構造時,則會自動調用移動構造函數。移動構造本質是將參數右值的資源竊取過來,占位已有,那么就不用做深拷貝了,所以它叫做移動構造。就是竊取別人的資源來構造自己,移動構造中沒有新開空間,拷貝數據,所以比拷貝構造更加高效。
與移動構造類似的還有移動賦值,也是通過右值引用來提高效率。
// 拷貝賦值 string& operator=(const string& s) {cout << "string& operator=(string s) -- 拷貝賦值(深拷貝)" << endl;string tmp(s);swap(tmp);return *this; } // 移動賦值 string& operator=(string&& s) {cout << "string& operator=(string s) -- 移動賦值(資源移動)" << endl;swap(s);return *this; }?
四、移動構造與移動賦值
那么移動構造和移動賦值有什么需要注意的地方嗎?
在C++98時,我們學習過C++的類中一共有6個默認成員函數(分別是構造函數、析構函數、拷貝構造函數、拷貝賦值重載、取地址重載、const取地址重載)。但隨著C++11的更新又新增了兩個默認成員函數,即移動構造函數和移動賦值重載。
注意情況
1. 若沒有自主實現移動構造函數,且沒有實現析構函數 、拷貝構造、拷貝賦值重載中的任意一個。那么編譯器會自動生成一個默認移動構造。默認生成的移動構造函數,對于內置類型成員會執行逐成員按字節拷貝,自定義類型成員則需要看這個成員是否存在移動構造,若存在就調用移動構造,不存在就調用拷貝構造。
2. 若沒有自主實現移動賦值重載函數,且沒有實現析構函數 、拷貝構造、拷貝賦值重載中的任意一個,那么編譯器會自動生成一個默認移動賦值。默認生成的移動構造函數,對于內置類型成員會執行逐成員按字節拷貝,自定義類型成員則需要看這個成員是否存在移動賦值,若存在就調用移動賦值,不存在就調用拷貝賦值。
3. 若提供了移動構造或者移動賦值中任意一個,編譯器不會自動提供拷貝構造和拷貝賦值。
五、萬能引用與完美轉發
5.1 萬能引用
模板中的&&不代表右值引用,而是萬能引用,其既能接收左值又能接收右值。
模板的萬能引用只是提供了能夠同時接收左值引用和右值引用的能力,但是引用類型就會被限制,在后續使用中都退化成了左值。所以萬能引用也被稱為引用折疊(即左值引用和右值引用都被折疊為左值)。
也可以換一種理解方式。在前面提到過右值引用的特性,右值引用是左值,且左值引用也是左值。所以不出意外,既能接收左值也能接收右值的萬能引用也是左值。
如果希望能夠在傳遞過程中保持它的左值或者右值的屬性, 就需要用到完美轉發
5.2 完美轉發
std::forward 完美轉發在傳參的過程中保留對象原生類型屬性
#include <iostream> using namespace std;void Fun(int& x) { cout << "左值引用" << endl; } void Fun(const int& x) { cout << "const 左值引用" << endl; } void Fun(int&& x) { cout << "右值引用" << endl; } void Fun(const int&& x) { cout << "const 右值引用" << endl; } template<typename T> void PerfectForward(T&& t) {Fun(std::forward<T>(t)); } int main() {PerfectForward(10); //右值引用int a;PerfectForward(a); //左值引用PerfectForward(std::move(a)); //右值引用const int b = 8;PerfectForward(b); //const 左值引用PerfectForward(std::move(b)); //const 右值引用return 0; }使用場景
在實際開發中,某些接口函數是提供了右值引用版本的,譬如STL中vector、list等容器的插入接口。傳入右值參數并被右值引用接收后,會被認為是左值,無法順利調用到移動構造和移動賦值等函數(沒有真正減少拷貝、提高效率),這時就需要使用完美轉發來在傳參過程中保證右值對象的屬性。
總結
- 上一篇: wifi模组论坛_华为太狠了!5G工业模
- 下一篇: Maven 打jar包部署到生产环境的p