C/C++编程:右值引用
右值引用不過是C++的一種新語法,重要的是基于右值引用引申處理的兩種C++編程技巧:移動語義和完美轉發
右值引用
C++98/03標準中就有引用,用&表示。但是此種引用方式有一個缺陷,即正常情況下只能操作C++中的左值,無法對右值添加引用。舉個例子:
int num = 10; int &b = num; //正確 int &c = 10; //錯誤如上所示,編輯器允許我們為num左值建立一個引用,但是不可以為10這個右值建立引用。因此,C++93/03標準中的引用又叫做“左值引用”
注意,雖然C++98/03標準不支持右值建立非常量左值引用,但是允許常量左值引用操作右值。也就是說,常量左值引用既可以操作左值,也可以操作右值。例如:
int num = 10; const int &b = num; const int &c = 10;我們知道,右值往往是沒有名稱的,因此要使用它只能借助引用的方式。這就產生一個問題,實際開發中我們可能需要對右值進行修改(實現移動語義時就需要),顯然左值引用的方式是行不通的
為此,C++11標準引入了另一種引用方式,稱為右值引用,用&&表示
C++標準委員會在選定右值引用符號時,既希望能選用現有 C++ 內部已有的符號,還不能與 C++ 98 /03 標準產生沖突,因此最終選定了 2 個 ‘&’ 表示右值引用。
需要注意的是,和聲明左值引用一樣,右值引用也必須立即進行初始化操作,而且只能使用右值進行初始化,比如:
int num = 10; //int && a = num; //右值引用不能初始化為左值 int && a = 10;和常量左值引用不同的是,右值引用還可以對右值進行修改。比如:
int && a = 10; a = 100; cout << a << endl;程序輸出結果為 100。
另外值得一提的是,C++ 語法上是支持定義常量右值引用的,例如:
const int&& a = 10;//編譯器不會報錯但這種定義出來的右值引用并無實際用處。
一方面,右值引用主要用于移動語義和完美轉發,其中前者需要有修改右值的權限;其次,常量右值引用的作用就是引用一個不可修改的右值,這項工作完全可以交給常量左值引用完成。
引入右值引用的原因除了我們可能需要對右值進行修改,還有另外一個原因,我們來看一個例子:
std::vector<int>foo(){std::vector<int> temp = {1, 2, 3, 4};return temp; }std::vector<int> v = foo();在這樣的代碼中,就傳統的理解而言,函數foo的返回值temp在內部創建然后被賦值給v,然而v獲得這個對象時,會將整個temp拷貝一份,然后把temp銷毀。如果這個temp非常大,這將造成大量額外的開銷(這也是傳統C++一直被詬病的問題)。在最后一行中,v是左值,foor()返回的值就是右值(也就是純右值),但是,v可以被別的變量俘獲到,而foo()嘗試的那個返回值作為一個臨時值,一旦被v復制后,將立即被銷毀,無法獲取,也不能修改。而將亡值就定義了這一行為:臨時的值能夠被識別、同時也能夠被移動
- 將亡值,是C++11為了引入右值引用而提出的概念(因此傳統C++中,純右值和右值是同一個概念),也就是即將被銷毀、卻能夠被移動的值
從C++11起,編譯器為我們做了一些工作,此處的temp會被進行此隱式右值轉換,等價于static_cast<std::vector<int>&&>(temp),進而此處的v會將foo局部返回的值進行移動(移動語義)
要拿到一個將亡值,就需要用到右值引用:T&&,其中T是類型。右值引用的聲明讓這個臨時值的生命周期得以延長,只要變量還活著,那么將網址將繼續存活
C++11提供了std::move這個方法將左值參數無條件的轉換為右值,有了它我們就可以方便的獲得一個右值臨時對象
rv2雖然引用了一個右值,但由于它是一個引用,所以rv2依舊是一個左值
注意,這里有一個歷史遺留問題:
int &a = std::move(1); //不合法,非常量左引用無法引用右值const int &b = std::move(1); // 合法,常量做引用允許引用右值學到這里,一些讀者可能無法記清楚左值引用和右值引用各自可以引用左值還是右值,這里給大家一張表格,方便大家記憶:
其實,C++11 標準中對右值做了更細致的劃分,分別稱為純右值(Pure value,簡稱 pvalue)和將亡值(eXpiring value,簡稱 xvalue )。其中純右值就是 C++98/03 標準中的右值(本節中已經做了大篇幅的講解),而將亡值則指的是和右值引用相關的表達式(比如某函數返回的 T && 類型的表達式)。對于純右值和將亡值,都屬于右值,讀者知道即可,不必深究。
使用右值引用接管數據
const引用
在C++語言中,引用是作為一種高效、安全的傳遞數據的方式而存在的。除了一般的引用類型,還可以聲明const引用
我們有以下一個Image類。
{ public:Image(int w, int h):width(w), height(h){data = new char[getSize()];}int getSize(){return width * height;} virtual ~Image(){if(data != nullptr){delete data;data = nullptr;width = 0;height = 0;}} private:int width = 0;int height = 0;char* data = nullptr; }上面只是這個類的雛形,只有析構函數、構造函數和取得數據大小的功能。
接下來編寫比較兩個Image是否相同的函數。最簡單的形式大致如下:
bool isSame(Image& img) {if(width == img.width&& height == img.height){return (memcmp(data,img.data,getSize())==0);}else{return false;} }這里使用引用類型的參數,避免了沒有必要的拷貝動作。當然我們還可以做的更好:由于比較函數沒有必要也不應該對比較對象的內容進行修改,所以還可以用下面的形式進行承諾:
bool isSame(const Image& img) {if(width == img.width&& height == img.height){char* in = static_cast<char*>(img.data);return (memcmp(data,in,getSize())==0);}else{return false;} }通過在參數前面增加const修飾符,向isSame方法的調用者保證,不會修改img的內容
右值引用
繼續添加將一個Image的一部分merge到另一個Image上的方法。函數的內容大致如下(這里忽略處理的細節):
void merge(Image& img){//接管img中的數據。img.height = 0;img.width = 0;img.data = nullptr; }類似的操作在處理在輸入對象時一般有兩種處理方式。有時希望只是參照而不破壞輸入數據,這時可以使用前面講到的為參數增加const修飾符的方式來承諾;有時為了提高效率或者其他的原因希望可以接管輸入的數據,就像上面代碼的狀態。這時的行為更像是數據移動。
對于第二種方式,如果僅僅定義一般的引用類型,利用者根本沒有辦法聲明來確定這個操作是否會接管參數中的數據,這種不確定性會造成恨到的麻煩。
這個時候就可以使用右值引用
void merge(Image&& img){//接管img中的數據。img.height = 0;img.width = 0;img.data = nullptr; }我們將參數聲明為右值引用,要求像一個臨時變量一樣的使用數據。使用這個函數的方法如下:
Image img1(100, 100); Image img2(100, 200); img1.merge(std::move(img2));注意代碼中的std::move,這是標準庫中提供的方法,它可以將左值顯式轉換為右值引用類型,從而告訴編譯器,可以向右值(臨時變量)一樣處理它。時也意味著接下來除了對img2賦值或銷毀以外,不再使用它。
C++11通過使用右值引用提供了一種接管數據的方法
總結
如果說使用const修飾符可以對外承諾不對參數進行修改的話,那么使用右值引用就是對外要求接管參數數據的權力
面試題
左值和右值有了解過嗎?
- 左值是一個內存實體,可以&,可以存在很久
- 右值沒有內存實體,只是臨時的,用一次就不用了。
可以用std::move將左值轉換為右值
說下你是怎么使用右值引用的
- 實現一個類的時候,會提供移動構造函數和移動賦值函數
- 怎么使用:如果發現某個對象需要賦值給一個新對象而且之前老對象不會不用了,就用std::move將左值轉換為右值
總結
以上是生活随笔為你收集整理的C/C++编程:右值引用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mit协议C语言,MIT Scheme
- 下一篇: 首次亮相数博会,维择科技带产品dCube