程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 淺析C++11中的右值援用、轉移語義和完善轉發

淺析C++11中的右值援用、轉移語義和完善轉發

編輯:關於C++

淺析C++11中的右值援用、轉移語義和完善轉發。本站提示廣大學習愛好者:(淺析C++11中的右值援用、轉移語義和完善轉發)文章只能為提供參考,不一定能成為您想要的結果。以下是淺析C++11中的右值援用、轉移語義和完善轉發正文


1. 左值與右值:

    C++關於左值和右值沒有尺度界說,然則有一個被普遍認同的說法:可以取地址的,著名字的,非暫時的就是左值;不克不及取地址的,沒著名字的,暫時的就是右值.

    可見立刻數,函數前往的值等都是右值;而非匿名對象(包含變量),函數前往的援用,const對象等都是左值.

    從實質上懂得,創立和燒毀由編譯器幕後掌握的,法式員只能確保在本行代碼有用的,就是右值(包含立刻數);而用戶創立的,經由過程感化域規矩可知其生計期的,就是左值(包含函數前往的部分變量的援用和const對象),例如:

int& foo(){int tmp; return tmp;}

int fooo(){int tmp; return tmp;}

int a=10;

const int b;

int& temp=foo();//固然正當,但temp援用了一個曾經不存在的對象

int tempp=fooo();

以上代碼中,a,temp和foo()都長短常量左值,b是常量左值,fooo()長短常量右值,10是常量右值,有一點要特殊留意:前往的援用是左值(可以取地址)!

普通來講,編譯器是不許可對右值停止更改的(由於右值的生計期不由法式員控制,即便更改了右值也未必可以用),關於內置類型對象特別如斯,但C++許可應用右值對象挪用成員函數,固然許可如許做,但出於異樣緣由,最好不要這麼做.

2. 右值援用:

    右值援用的表現辦法為

 Datatype&& variable

    右值援用是C++ 11新增的特征,所以C++ 98的援用為左值援用.右值援用用來綁定到右值,綁定到右值今後原來會被燒毀的右值的生計期會延伸至與綁定到它的右值援用的生計期,右值援用的存在其實不是為了代替左值援用,而是充足應用右值(特殊是暫時對象)的建構來削減對象建構和析構操作以到達進步效力的目標,例如關於以下函數:

(Demo是一個類)
Demo foo(){ 
  Demo tmp;
  return tmp;
}

在編譯器不停止RVO(return value optimization)優化的條件下以下操作:

Demo x=foo();

將會挪用三次結構函數(tmp的,x的,暫時對象的),響應的在對象被燒毀時也會挪用三次析構函數,而假如采取右值援用的方法:

Demo&& x=foo();

那末就不須要停止x的建構,原來原來要被燒毀的暫時對象也會因為x的綁定而將生計期延伸至和x一樣(可以懂得為x付與了誰人暫時對象一個正當位置:一個名字),就須要進步了效力(價值就是tmp須要占領4字節空間,但這是眇乎小哉的).

    右值援用與左值援用綁定例則:

         常量左值援用可以綁定到常量和異常量左值,常量和異常量右值;

         異常量左值援用只能綁定到異常量左值;

         異常量右值援用只能綁定到異常量右值(vs2013也能夠綁定到常量右值);

         常量右值援用只能綁定到常量和異常量右值(異常量右值援用只是為了語義的完全而存在,常量左值援用便可以完成它的感化).

         固然從綁定例則中可以看出常量左值援用也能夠綁定到右值,但明顯弗成以轉變右值的值,右值援用便可以,從而完成轉移語義,由於右值援用平日要轉變所綁定的右值,所以被綁定的右值不克不及為const.

    留意:右值援用是左值!

3. 轉移語義(move semantics):

    右值援用被引入的目標之一就是完成轉移語義,轉移語義可以將資本 ( 堆,體系對象等 ) 的一切權從一個對象(平日是匿名的暫時對象)轉移到另外一個對象,從而削減對象構建及燒毀操作,進步法式效力(這在2的例子中曾經作懂得釋).轉移語義與拷貝語義是絕對的.從轉移語義可以看出,現實上,轉移語義其實不是新的概念,它現實上曾經在C++98/03的說話和庫中被應用了,好比在某些情形下拷貝結構函數的省略(copy constructor elision in some contexts),智能指針的拷貝(auto_ptr “copy”),鏈表拼接(list::splice)和容器內的置換(swap on containers)等,只是還沒有同一的語法和語義支撐

    固然通俗的函數和操作符也能夠應用右值援用完成轉移語義(如2中的例子),但轉移語義平日是經由過程轉移結構函數和轉移賦值操作符完成的.轉移結構函數的原型為Classname(Typename&&) ,而拷貝結構函數的原型為Classname(const Typename&) ,轉移結構函數不會被編譯器主動生成,須要本身界說,只界說轉移結構函數也不影響編譯器生成拷貝結構函數,假如傳遞的參數是左值,就挪用拷貝結構函數,反之,就挪用轉移結構函數.

例如:

class Demo{

public:

  Demo():p(new int[10000]{};

  Demo(Demo&& lre):arr(lre.arr),size(lra.size){lre.arr=NULL;}//轉移結構函數

  Demo(const Demo& lre):arr(new int[10000]),size(arr.size){

    for(int cou=0;cou<10000;++cou)

      arr[cou]=lew.arr[cou];

  }

private:

  int size;

  int* arr;

}

    從以上代碼可以看出,拷貝結構函數在堆中從新開拓了一個年夜小為10000的int型數組,然後每一個元素分離拷貝,而轉移結構函數則是直接接收參數的指針所指向的資本,效力弄下立判!須要留意的是轉移結構函數實參必需是右值,普通是暫時對象,如函數的前往值等,關於此類暫時對象普通在當行代碼以後就被燒毀,而采取轉移結構函數可以延伸其性命期,可謂是物盡其用,同時有防止了從新開拓數組.關於上述代碼中的轉移結構函數,有需要具體剖析一下:

Demo(Demo&& lre):arr(lre.arr),size(lre.size)({lre.arr=NULL;}

lre是一個右值援用,經由過程它直接拜訪實參(暫時對象)的資本來完成資本轉移,lre綁定的對象(必需)是右值,但lre自己是左值;

由於lre是函數的部分對象,”lre.arr=NULL"必弗成少,不然函數開頭挪用析構函數燒毀lre時依然會將資本釋放,轉移的資本照樣被體系發出.

4. move()函數

    3中的例子並不是全能,Demo(Demo&& lre)的實參必需是右值,有時刻一個左值行將達到生計期,然則依然想要應用轉移語義接收它的資本,這時候就須要move函數.

    std::move函數界說在尺度庫<utility>中,它的感化是將左值強行轉化為右值應用,從完成上講,std:move同等於static_cast<T&&>(lvalue) ,由此看出,被轉化的左值自己的生計期和左值屬性並沒有被轉變,這相似於const_cast函數.是以被move的實參應當是行將達到生計期的左值,不然的話能夠起到不和後果.

5. 完善轉發(perfect forwarding)

    完善轉發指的是將一組實參"完善"地傳遞給形參,完善指的是參數的const屬性與閣下值屬性不變,例如在停止函數包裝的時刻,func函數存鄙人列重載:

void func(const int);
void func(int);
void func(int&&);

假如要將它們包裝到一個函數cover內,以完成:

void cover(typename para){
  func(para);
}

使得針對分歧實參能在cover內挪用響應類型的函數,仿佛只能經由過程對cover停止函數重載,這使代碼變得冗繁,另外一種辦法就是應用函數模板,但在C++ 11之前,完成該功效的函數模板只能采取值傳遞,以下:

template<typename T>
void cover(T para){
  ...
  func(para);
  ...
}

但假如傳遞的是一個相當年夜的對象,又會形成效力成績,要經由過程援用傳遞完成形介入實參的完善婚配(包裹const屬性與閣下值屬性的完善婚配),就要應用C++ 11 新引入的援用折疊規矩:

函數形參       T的類型         推導後的函數形參

T&               A&                A&
T&               A&&              A&
T&&             A&                A&
T&&             A&&              A&&

 是以,關於前例的函數包裝請求,采取以下模板便可以處理:

template<typename T>
void cover(T&& para){
  ...
  func(static_cast<T &&>(para));
  ...
}

 

假如傳入的是左值援用,轉發函數將被實例化為:

void func(T& && para){

  func(static_cast<T& &&>(para));

}

運用援用折疊,就為:

void func(T& para){

  func(static_cast<T&>(para));

}

假如傳入的是右值援用,轉發函數將被實例化為:

void func(T&& &¶){

   func(static_cast<T&& &&>(para));
}

運用援用折疊,就是:

void func(T&& para){

  func(static_cast<T&&>(para));

}

關於以上的static_cast<T&&> ,現實上只在para被推導為右值援用的時刻才施展感化,因為para是左值(右值援用是左值),是以須要將它轉為右值後再傳入func內,C++ 11在<untility>界說了一個std::forward<T>函數來完成以下行為,

所以終究版本為

template<typename T>

void cover(T&& para){

  func(forward(forward<T>(para)));

}

std::forward的完成與static_cast<T&&>(para)稍有分歧

std::forward函數的用法為forward<T>(para) , 若T為左值援用,para將被轉換為T類型的左值,不然para將被轉換為T類型右值

總結

以上就是關於C++11中右值援用、轉移語義和完善轉發的全體內容,這篇文章引見的很具體,願望對年夜家的進修任務能有所贊助。

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