C++11——右值引用
目錄
前言
一.右值引用的概念
? ? ? ? 1.1 左值和右值的概念
? ? ? ? 1.2 引用和右值引用比較
二.右值引用的作用
? ? ? ? 2.1引用的缺陷
? ? ? ? 2.1 移動語義
????????2.2 右值引用的具體應用
? ? ? ? 2.3 對比引用總結
三.右值引用引用左值(move)
四.完美轉化
前言
? ? ? ? 在C++98中也有一個引用,為左值引用,但是左值引用有一些不足。而C++11的右值引用的提出彌補了左值引用的不足。
? ? ? ? 左值引用和右值引用都是別名。
? ? ? ? 說明:下面說引用都代表C++98的左值引用。
一.右值引用的概念
? ? ? ? 右值引用也是一塊空間的別名。只能對右值進行引用。使用是在類型后面加兩個&&。
#include<iostream> using namespace std;int Add(int x, int y){return x + y; }int main(){const int&& ra = 10;//右值引用//函數(shù)返回值為一個臨時變量,是右值int&& ret = Add(2, 3);//右值引用return 0; }? ? ? ? 1.1 左值和右值的概念
? ? ? ? 左值和右值是C語言的概念,但是C語言沒有給出嚴格的標準,一般認為,可以放在等號"="左邊的是左值,可以放在等號右邊的是右值。但是這個說法是錯誤的。
比如:
#include<iostream> using namespace std;int main(){//a,b是左值,10和20是右值int a = 10;int b = 20;//此時b也可以放在等號右邊//a也可以放在等號右邊a = b;b = a;return 0; }左值和右值是不好區(qū)分的,這里我們一般這樣認為:
- 左值:一般是可以修改的值,可以取地址的,通常是變量。
- 右值:一般是常量(除const 修飾的),表達式或者函數(shù)的傳值返回(生成臨時變量)。
????????注意:傳引用返回是右值。
C++11有對右值進行了嚴格的區(qū)分:
- 純右值:比如常量,表達式值a+b
- 將亡值:比如函數(shù)傳值返回,表達式的中間結果。顧名思義,將亡值的空間馬上就要被釋放了。
? ? ? ? 1.2 引用和右值引用比較
- 引用,只能引用左值,不能引用右值。但是const引用既可以引用左值,又可以引用右值。
- 右值引用:只能引用右值,不能引用左值。但是右值引用可以引用move之后的左值。move在后面有介紹,可以認為是改變了左值的屬性,變成了右值。
二.右值引用的作用
? ? ? ? 右值引用和引用都是別名,為什么還要提出右值引用呢?
? ? ? ? 2.1引用的缺陷
#include<iostream> using namespace std;class String{ private:char *_str; public://構造String(char *str = " "){_str = new char[strlen(str) + 1];strcpy(_str, str);}//拷貝構造String(const String& s){cout << "String(const String& s)——拷貝構造" << endl;_str = new char[strlen(s._str) + 1];strcpy(_str, s._str);}};String Fun(String& s){String ret(s);return ret; } ~String() {if(_str)delete[] _str; } int main(){String s1("左值");String s2 = Fun(s1);getchar();return 0; }? ? ? ? 我們知道引用做參數(shù)和返回值是可以減少拷貝構造,特別是對于深拷貝的,可以提高效率。但是,當返回值是函數(shù)的局部對象時,不能引用返回,需要傳值返回。
//需要傳值返回 String Fun(String& s){String ret(s);return ret; }? ? ? ? 當將返回值函數(shù)返回值賦給另外一個對象s2時。 會調(diào)用String類的拷貝構造函數(shù),進行深拷貝。
String s1("左值"); String s2 = Fun(s1);? ? ? ret在按照值返回時,必須拷貝構造一個臨時對象,需要進行深拷貝。將Fun函數(shù)返回值賦值給s2時,也就是將臨時對象拷貝構造s2,也需要進行深拷貝。仔細觀察發(fā)現(xiàn):s2和臨時對象空間里的內(nèi)容是相同的,而它們?nèi)齻€都有獨立的空間,相當于創(chuàng)建了三個完全相同的對象。這樣對于空間來說是一種浪費,程序效率也會降低。
? ? ? ? 下面來說如何優(yōu)化。
? ? ? ? 2.1 移動語義
- 移動語義:將一個對象中的資源移動到另外一個對象中。
如上缺陷,我們可以這樣優(yōu)化:
? ? ? ? 我們知道臨時變量是內(nèi)容和s2的內(nèi)容相同,而臨時對象是一個將亡值。我們可以在臨時對象拷貝構造s2時,不進行深拷貝,而是將臨時對象空間的資源換給對象s2。
? ? ? ? 臨時對象是將亡值,也就是右值,我們可以重載一個參數(shù)為右值引用的拷貝構造函數(shù)。我們將這個拷貝構造函數(shù)稱為移動構造。
????????參數(shù)s右值引用的是臨時變量。將臨時變量的資源移動到this指針,也就是s2中。而臨時對象在構造完s2后,就會被銷毀。
//移動構造 String(String&& s){_str = s._strs._str = nullptr; }整個過程:
? ? ? ? 由于ret是左值,在拷貝構造臨時對象時,調(diào)用的是拷貝構造函數(shù),而臨時對象是右值(將亡值),在構造s2時,會調(diào)用移動構造,將臨時對象的資源換給了s2。這樣減少了一次深拷貝的過程。
#include<iostream>using namespace std;class String{ private:char *_str; public:String(char *str = " "){_str = new char[strlen(str) + 1];strcpy(_str, str);}//拷貝構造String(const String& s){cout << "String(const String& s)——拷貝構造" << endl;_str = new char(strlen(s._str) + 1);strcpy(_str, s._str);}//移動構造String(String&& s){cout << "String(const String& s)——移動構造" << endl;_str = s._str;s._str = nullptr;}~String(){if (_str)delete[] _str;}};//需要傳值返回 String Fun(String& s){String ret(s);//調(diào)用拷貝構造return ret; }int main(){String s1("左值");String s2(Fun(s1));//調(diào)用移動構造return 0; }?這里編譯器做了優(yōu)化,ret調(diào)用拷貝構造函數(shù)構造臨時對象的過程省略了。
注意:
- 在移動構造函數(shù)的參數(shù)一定不能設置成const類型的右值引用,否則不能修改,資源無法轉移。
- 在C++11中,編譯器會為了類默認生成一個移動構造,該移動構造為淺拷貝,因此當類中涉及到資源管理,必須顯示自己的移動構造。
????????2.2 右值引用的具體應用
????????右值引用的主要應用就是重載了移動構造函數(shù),利用了將亡值,將將亡值的空間內(nèi)容交換到要拷貝的對象中。減少了深拷貝。
- 右值引用做函數(shù)的參數(shù)
? ? ? ? 由于右值引用引用的是右值(將亡值),當函數(shù)體里需要對該參數(shù)進行拷貝構造時,會調(diào)用移動拷貝構造。減少深拷貝。提高效率。
- 函數(shù)傳值返回,用對象接收。
? ? ? ? 函數(shù)傳值返回,返回一個臨時對象,是一個將亡值。再用對象接收,臨時對象拷貝構造對象。會調(diào)用移動拷貝構造函數(shù),減少深拷貝。
? ? ? ? 2.3 對比引用總結
????????引用和右值引用本質的作用都是減少拷貝。右值引用彌補了引用的不足。右值引用提高了傳值返回的效率。
引用:
? ? ? ? 引用做參數(shù)和返回值可以減少拷貝構造。但是,當返回的對象出了作用域就不在了,只能傳值返回。
? ? ? ? 如果沒有右值引用,用對象接收,會調(diào)用拷貝構造,對于string/vector等容器,需要進行深拷貝。效率低。
右值引用:
? ? ? ?右值引用的主要應用就是重載了移動構造函數(shù),利用了將亡值,將將亡值的空間內(nèi)容交換到要拷貝的對象中。減少了深拷貝。
? ? ? ? 當函數(shù)傳值返回時,用對象接收,會調(diào)用移動構造函數(shù),對于string/vector等容器,不需要進行深拷貝,只需要將右值引用交換到構造的對象中即
? ? ? ? 右值引用做參數(shù),函數(shù)體里有需要拷貝構造右值引用的,會調(diào)用移動構造,不會進行深拷貝。
三.右值引用引用左值(move)
? ? ? ? 按照語法,右值引用只能引用右值。但是在有些場景下,需要用到右值去引用左值來實現(xiàn)移動語義。
? ? ? ? 當右值引用一個左值,需要通過move函數(shù)將左值轉化為右值。可以理解成將一個左值的屬性改成右值返回。
int main(){//a為左值,10為右值int a = 10;int&& ra1 = 10;int&& ra2 = move(a);//move后就可以了return 0; }????????注意:被轉化的左值,其生命周期并沒有隨著左值的轉化而改變,move并不會銷毀左值。但是,move后,會改變左值的內(nèi)容。如果后序還會使用到左值,要慎用move。
? ? ? ? 在STL中也有一個move函數(shù),作用時將一個范圍中的元素搬到另外一個位置。
如下:用的上面這個類。
四.完美轉化
? ? ? ? 轉發(fā)是:按照模板參數(shù)的類型,將參數(shù)傳遞給函數(shù)模板中調(diào)用的另外一個函數(shù)。
#include<iostream> using namespace std;void Fun(int &x){ cout << "lvalue ref" << endl; } void Fun(int &&x){ cout << "rvalue ref" << endl; }template<typename T> void PerfectForward(T &&t){Fun(t); }int main() {PerfectForward(10); //10是右值return 0; }?? ? ? ? 下面PerfectForward()是轉化的模板函數(shù),Func為實際目標函數(shù)。但是這里有一個問題。如下:10是右值,但是調(diào)用的確是左值引用的函數(shù)。說明在PerfectForward()模板函數(shù)轉發(fā)過程中,10的右值屬性丟失了。
?????????完美轉發(fā):是目標函數(shù)總希望參數(shù)的實際類型不會因為轉化函數(shù)而發(fā)生改變。就好像轉化函數(shù)不存在。也就是,當函數(shù)模板在向其它函數(shù)傳遞自身的形參時,如果相應實參時左值,它轉化的就是左值,如果相應實參是右值,它轉化的就是右值。
? ? ? ? 完美轉化需要通過forward函數(shù)來實現(xiàn)。
void Fun(int &x){ cout << "lvalue ref" << endl; } void Fun(int &&x){ cout << "rvalue ref" << endl; }template<typename T> void PerfectForward(T &&t){Fun(forward<T>(t)); //在需要轉化函數(shù)的的目標函數(shù)參數(shù)調(diào)用forward函數(shù) } int main() {PerfectForward(10); // rvalue refsystem("pause");return 0; }總結
以上是生活随笔為你收集整理的C++11——右值引用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 无需标注,这个AI能在大量数据中一眼识别
- 下一篇: Maven学习文档常用命令继承聚合