程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> c++11之右值引用

c++11之右值引用

編輯:C++入門知識

本文大部分來自這裡,並不是完全著行翻譯,如有不明白的地方請參考原文。

在c++中,創建臨時對象的開銷對程序的影響一直很大,比如以下這個例子:

 name = getName();

name對象的構建可以細分為3步:

1. 用kian構建函數內的局部string對象tmp1

2. 調用復制構造函數將tmp1復制到tmp2,並析構tmp1.

3. 調用賦值拷貝函數將tmp2拷貝到name,並析構tmp2。

所以一共做了3次內存分配,兩次復制拷貝操作,但是tmp1和tmp2都馬上析構了,如果內存分配很大的話,這裡的資源浪費是很可觀的。在c++11之前,編譯器已經會做一些優化了,比如返回值優化RVO(return value optimization)優化了第二步,省略了構建tmp2的開銷,但是第3步直到c++11引入移動語義後才得到了徹底解決。移動語義依賴於右值引用實現,要了解移動語義,必須先要明白什麼是右值和左值。

 

c++11中存在右值和左值。左值可以取地址,是相對永久的對象,可以被賦值,比如

=; //a  a lvalue

左值也可以不是變量,如

&= ;

相應地,右值是一個臨時對象,不可以取地址。



getRef()的值是個右值,它不是x的引用,而是x的拷貝,是一個臨時存在的對象。

 

在c++11之前,可以使用const引用綁定到臨時對象上,

 & name = getName(); 
& name = getName(); 

不可以改變一個即將消失的對象,所以將非const引用綁定到臨時對象上是不允許的。到了c++11,引進了右值引用&&,

 && name = getName(); 
&& name = getName(); 

左值和右值最重要的區別在於做為函數參數時,

printReference ( String&<<&&<<

前者可以接受任何參數,而後者只可以接收右值,及臨時對象,

 me( 
printReference( getName() ); 

 

移動構造函數接收一個臨時對象作為參數,直接獲取臨時對象內部資源,避免重新分配內存。

假設我們有這樣一個簡單的ArrayWrapper類:

 
ArrayWrapper ( ArrayWrapper&  (  i = ; I < _size; ++=~ *

可以看出復制構造函數每次都需要分配內存並著個賦值,這是非常消耗資源的,我們加一個移動構造函數,

ArrayWrapper (ArrayWrapper&&== 

移動構造函數比復制構造函數簡單多了,不過要注意兩點,

1. 參數必須是非const右值引用

2. 必須將other._p_vals設為NULL

參數不設為非const,就不能將other._p_vals設為NULL,為什麼要設為NULL?,因為 other是一個即將消失的右值,調用其析構函數會釋放_p_vals指向的內存,不設置為NULL,我們得到的對象就會指向垃圾內存。

因為參數是非const的,不能接收const右值,所以千萬不要這樣返回需要使用的右值。

const ArrayWrapper getArrayWrapper (); // makes the move constructor useless, the temporary is const!

如果對象內部包含另一個對象,移動構造函數內會發生什麼?假設ArrayWrapper包含的不只有_size,而是更詳細的數據,比如

 size,  std::&
MetaData ( MetaData&
MetaData (MetaData&& getName ()  {  getSize ()  { 

那麼ArrayWrapper需要修改成這樣,

 [  ] ), _metadata( ,  [ n ] ), _metadata( n, 
ArrayWrapper (ArrayWrapper&&=
ArrayWrapper ( ArrayWrapper&  (  i = ; i< _metadata.getSize(); ++=~ *

當 ArrayWrapper調用移動構造函數時,_metadata調用的是移動構造函數還是復制構造函數?表面看應該是移動構造函數,因為ArrayWrapper的移動構造函數參數other 是右值引用,但要注意的是,右值引用不是右值!右值引用是一個左值,other._metadata也是一個左值,所以_metadata調用的是復制構造函數。

 

怎樣讓_metadata也調用移動構造函數,我們需要使用std::move,move並不移動任何東西,只是將對象轉換為右值。使用move後,代碼就是這樣的,

ArrayWrapper (ArrayWrapper&&=&&

move的功能很神奇吧,它是用什麼新技術將對象轉為右值的呢?事實是它用的是c++一直都有的static_cast轉換符。下面是它的源碼,

template <typename _Tp>
<P>inline typename ;std::remove_reference<_Tp>:::type&&&&& static_cast<typename std::remove_reference<_Tp>::type&&> (__t);}

看到它的第一感覺是它應該只能接收右值引用啊,為什麼左值沒有問題?

= std:move((“zhang”)); 
s2 = std:move(s1); 

通常我們不能將右值引用綁定到左值上,不過為了支持move語義,c++11定義了兩個例外:

1. 當將左值引用傳遞給函數的右值引用參數,且此右值引用指向模板類型參數(如_Tp&&),編譯器推斷模板類型參數為實參的左值引用類型。即在std:move(s1)中,

_Tp推斷為string&,那麼string& &&又是什麼?這由第二條確定例外定義

2. 引用的引用形成折疊,x& &,x& &&, x&& &都折疊成x&;只有x&& &&折疊成x&&。

所以std:move(s1)會實例化

string&& move(string& t)

而std:move(string(“zhang”))實例化

string&& move(string&& t)

 

參考:

http://www.cprogramming.com/c++11/rvalue-references-and-move-semantics-in-c++11.html

http://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved