亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > C++ > 正文

C++11右值引用和轉發型引用教程詳解

2020-05-23 13:34:02
字體:
來源:轉載
供稿:網友

右值引用

為了解決移動語義及完美轉發問題,C++11標準引入了右值引用(rvalue reference)這一重要的新概念。右值引用采用T&&這一語法形式,比傳統的引用T&(如今被稱作左值引用 lvalue reference)多一個&。

如果把經由T&&這一語法形式所產生的引用類型都叫做右值引用,那么這種廣義的右值引用又可分為以下三種類型:

  • 無名右值引用
  • 具名右值引用
  • 轉發型引用

無名右值引用和具名右值引用的引入主要是為了解決移動語義問題。

轉發型引用的引入主要是為了解決完美轉發問題。 

無名右值引用

無名右值引用(unnamed rvalue reference)是指由右值引用相關操作所產生的引用類型。

無名右值引用主要通過返回右值引用的類型轉換操作產生, 其語法形式如下:

static_cast<T&&>(t)

標準規定該語法形式將把表達式 t 轉換為T類型的無名右值引用。

無名右值引用是右值,標準規定無名右值引用和傳統的右值一樣具有潛在的可移動性,即它所占有的資源可以被移動(竊?。?。 

std::move()

由于無名右值引用是右值,借助于類型轉換操作產生無名右值引用這一手段,左值表達式就可以被轉換成右值表達式。為了便于利用這一重要的轉換操作,標準庫為我們提供了封裝這一操作的函數,這就是std::move()。

假設左值表達式 t 的類型為T&,利用以下函數調用就可以把左值表達式 t 轉換為T類型的無名右值引用(右值,類型為T&&)。
std::move(t)

具名右值引用

如果某個變量或參數被聲明為T&&類型,并且T無需推導即可確定,那么這個變量或參數就是一個具名右值引用(named rvalue reference)。

具名右值引用是左值,因為具名右值引用有名字,和傳統的左值引用一樣可以用操作符&取地址。

與廣義的右值引用相對應,狹義的右值引用僅限指具名右值引用。

傳統的左值引用可以綁定左值,在某些情況下也可綁定右值。與此不同的是,右值引用只能綁定右值。

右值引用和左值引用統稱為引用(reference),它們具有引用的共性,比如都必須在初始化時綁定值,都是左值等等。

struct X {}; X a; X&& b = static_cast<X&&>(a); X&& c = std::move(a); //static_cast<X&&>(a) 和 std::move(a) 是無名右值引用,是右值 //b 和 c 是具名右值引用,是左值 X& d = a; X& e = b; const X& f = c; const X& g = X(); X&& h = X(); //左值引用d和e只能綁定左值(包括傳統左值:變量a以及新型左值:右值引用b) //const左值引用f和g可以綁定左值(右值引用c),也可以綁定右值(臨時對象X()) //右值引用b,c和h只能綁定右值(包括新型右值:無名右值引用std::move(a)以及傳統右值:臨時對象X()) 

左右值重載策略

有時我們需要在函數中區分參數的左右值屬性,根據參數左右值屬性的不同做出不同的處理。適當地采用左右值重載策略,借助于左右值引用參數不同的綁定特性,我們可以利用函數重載來做到這一點。常見的左右值重載策略如下:

struct X {}; //左值版本 void f(const X& param1){/*處理左值參數param1*/} //右值版本 void f(X&& param2){/*處理右值參數param2*/} X a; f(a);      //調用左值版本 f(X());     //調用右值版本 f(std::move(a)); //調用右值版本 

即在函數重載中分別重載const左值引用和右值引用。

重載const左值引用的為左值版本,這是因為const左值引用參數能綁定左值,而右值引用參數不能綁定左值。

重載右值引用的為右值版本,這是因為雖然const左值引用參數和右值引用參數都能綁定右值,但標準規定右值引用參數的綁定優先度要高于const左值引用參數。

移動構造器和移動賦值運算符

在類的構造器和賦值運算符中運用上述左右值重載策略,就會產生兩個新的特殊成員函數:移動構造器(move constructor)和移動賦值運算符(move assignment operator)。

struct X {   X();             //缺省構造器   X(const X& that);      //拷貝構造器   X(X&& that);         //移動構造器   X& operator=(const X& that); //拷貝賦值運算符   X& operator=(X&& that);   //移動賦值運算符 }; X a;               //調用缺省構造器 X b = a;             //調用拷貝構造器 X c = std::move(b);       //調用移動構造器 b = a;              //調用拷貝賦值運算符 c = std::move(b);        //調用移動賦值運算符 

移動語義

無名右值引用和具名右值引用的引入主要是為了解決移動語義問題。

移動語義問題是指在某些特定情況下(比如用右值來賦值或構造對象時)如何采用廉價的移動語義替換昂貴的拷貝語義的問題。

移動語義(move semantics)是指某個對象接管另一個對象所擁有的外部資源的所有權。移動語義需要通過移動(竊?。┢渌?/p>

對象所擁有的資源來完成。移動語義的具體實現(即一次that對象到this對象的移動(move))通常包含以下若干步驟:

  • 如果this對象自身也擁有資源,釋放該資源
  • 將this對象的指針或句柄指向that對象所擁有的資源
  • 將that對象原本指向該資源的指針或句柄設為空值
  •  

上述步驟可簡單概括為①釋放this(this非空時)②移動that

移動語義通常在移動構造器和移動賦值運算符中得以具體實現。兩者的區別在于移動構造對象時this對象為空因而①釋放this無須進行。

與移動語義相對,傳統的拷貝語義(copy semantics)是指某個對象拷貝(復制)另一個對象所擁有的外部資源并獲得新生資源的所有權。拷貝語義的具體實現(即一次that對象到this對象的拷貝(copy))通常包含以下若干步驟:

  • 如果this對象自身也擁有資源,釋放該資源
  • 拷貝(復制)that對象所擁有的資源
  • 將this對象的指針或句柄指向新生的資源
  • 如果that對象為臨時對象(右值),那么拷貝完成之后that對象所擁有的資源將會因that對象被銷毀而即刻得以釋放

上述步驟可簡單概括為①釋放this(this非空時)②拷貝that③釋放that(that為右值時)
拷貝語義通常在拷貝構造器和拷貝賦值運算符中得以具體實現。兩者的區別在于拷貝構造對象時this對象為空因而①釋放this無須進行。

比較移動語義與拷貝語義的具體步驟可知,在賦值或構造對象時,

如果源對象that為左值,由于兩者效果不同(移動that ≠ 拷貝that),此時移動語義不能用來替換拷貝語義。

如果源對象that為右值,由于兩者效果相同(移動that = 拷貝that + 釋放that),此時廉價的移動語義(通過指針操作來移動資源)便可以用來替換昂貴的拷貝語義(生成,拷貝然后釋放資源)。

由此可知,只要在進行相關操作(比如賦值或構造)時,采取適當的左右值重載策略區分源對象的左右值屬性,根據其左右值屬性分別采用拷貝語義和移動語義,移動語義問題便可以得到解決。

下面用MemoryBlock這個自我管理內存塊的類來具體說明移動語義問題。

#include <iostream> class MemoryBlock { public:   // 構造器(初始化資源)   explicit MemoryBlock(size_t length)     : _length(length)     , _data(new int[length])   {   }   // 析構器(釋放資源)   ~MemoryBlock()   {     if (_data != nullptr)     {       delete[] _data;     }   }   // 拷貝構造器(實現拷貝語義:拷貝that)   MemoryBlock(const MemoryBlock& that)     // 拷貝that對象所擁有的資源     : _length(that._length)     , _data(new int[that._length])   {     std::copy(that._data, that._data + _length, _data);   }   // 拷貝賦值運算符(實現拷貝語義:釋放this + 拷貝that)   MemoryBlock& operator=(const MemoryBlock& that)   {     if (this != &that)     {       // 釋放自身的資源       delete[] _data;       // 拷貝that對象所擁有的資源       _length = that._length;       _data = new int[_length];       std::copy(that._data, that._data + _length, _data);     }     return *this;   }   // 移動構造器(實現移動語義:移動that)   MemoryBlock(MemoryBlock&& that)     // 將自身的資源指針指向that對象所擁有的資源     : _length(that._length)     , _data(that._data)   {     // 將that對象原本指向該資源的指針設為空值     that._data = nullptr;     that._length = 0;   }   // 移動賦值運算符(實現移動語義:釋放this + 移動that)   MemoryBlock& operator=(MemoryBlock&& that)   {     if (this != &that)     {       // 釋放自身的資源       delete[] _data;       // 將自身的資源指針指向that對象所擁有的資源       _data = that._data;       _length = that._length;       // 將that對象原本指向該資源的指針設為空值       that._data = nullptr;       that._length = 0;     }     return *this;   } private:   size_t _length; // 資源的長度   int* _data; // 指向資源的指針,代表資源本身 }; MemoryBlock f() { return MemoryBlock(50); } int main() {   MemoryBlock a = f();      // 調用移動構造器,移動語義   MemoryBlock b = a;       // 調用拷貝構造器,拷貝語義   MemoryBlock c = std::move(a);  // 調用移動構造器,移動語義   a = f();            // 調用移動賦值運算符,移動語義   b = a;             // 調用拷貝賦值運算符,拷貝語義   c = std::move(a);        // 調用移動賦值運算符,移動語義 } 

轉發型引用

如果某個變量或參數被聲明為T&&類型,并且T需要經過推導才可確定,那么這個變量或參數就是一個轉發型引用(forwarding reference)。

轉發型引用由以下兩種語法形式產生

  • 如果某個變量被聲明為auto&&類型,那么這個變量就是一個轉發型引用
  • 在函數模板中,如果某個參數被聲明為T&&類型,并且T是一個需要經過推導才可確定的模板參數類型,那么這個參數就是一個轉發型引用

轉發型引用是不穩定的,它的實際類型由它所綁定的值來確定。轉發型引用既可以綁定左值,也可以綁定右值。如果綁定左值,轉發型引用就成了左值引用。如果綁定右值,轉發型引用就成了右值引用。
轉發型引用在被C++標準所承認之前曾經被稱作萬能引用(universal reference)。萬能引用這一術語的發明者,Effective C++系列的作者Scott Meyers認為,如此異常靈活的引用類型不屬于右值引用,它應該擁有自己的名字。

對于某個轉發型引用類型的變量(auto&&類型)來說

  • 如果初始化表達式為左值(類型為U&),該變量將成為左值引用(類型為U&)。
  • 如果初始化表達式為右值(類型為U&&),該變量將成為右值引用(類型為U&&)。

對于函數模板中的某個轉發型引用類型的形參(T&&類型)來說

  • 如果對應的實參為左值(類型為U&),模板參數T將被推導為引用類型U&,該形參將成為左值引用(類型為U&)。
  • 如果對應的實參為右值(類型為U&&),模板參數T將被推導為非引用類型U,該形參將成為右值引用(類型為U&&)。
struct X {}; X&& var1 = X();              // var1是右值引用,只能綁定右值X() auto&& var2 = var1;            // var2是轉發型引用,可以綁定左值var1                       // var2的實際類型等同于左值var1,即X& auto&& var3 = X();             // var3是轉發型引用,可以綁定右值X()                       // var3的實際類型等同于右值X(),即X&& template<typename T> void g(std::vector<typename T>&& param1); // param1是右值引用 template<typename T> void f(T&& param2);            // param2是轉發型引用 X a; f(a);        // 模板函數f()的形參param2是轉發型引用,可以綁定左值a            // 在此次調用中模板參數T將被推導為引用類型X&            // 而形參param2的實際類型將等同于左值a,即X& f(X());       // 模板函數f()的形參param2是轉發型引用,可以綁定右值X()            // 在此次調用中模板參數T將被推導為非引用類型X            // 而形參param2的實際類型將等同于右值X(),即X&& // 更多右值引用和轉發型引用 const auto&& var4 = 10;              // 右值引用 template<typename T> void h(const T&& param1);             // 右值引用 template <typename T/*, class Allocator = allocator*/> class vector { public:   void push_back( T&& t );           // 右值引用   template <typename Args...>   void emplace_back( Args&&... args );     // 轉發型引用 }; 

完美轉發

完美轉發(perfect forwarding)問題是指函數模板在向其他函數轉發(傳遞)自身參數(形參)時該如何保留該參數(實參)的左右值屬性的問題。也就是說函數模板在向其他函數轉發(傳遞)自身形參時,如果相應實參是左值,它就應該被轉發為左值;同樣如果相應實參是右值,它就應該被轉發為右值。這樣做是為了保留在其他函數針對轉發而來的參數的左右值屬性進行不同處理(比如參數為左值時實施拷貝語義;參數為右值時實施移動語義)的可能性。如果將自身參數不分左右值一律轉發為左值,其他函數就只能將轉發而來的參數視為左值,從而失去針對該參數的左右值屬性進行不同處理的可能性。

轉發型引用的引入主要是為了解決完美轉發問題。在函數模板中需要保留左右值屬性的參數,也就是要被完美轉發的參數須被聲明為轉發型引用類型,即參數必須被聲明為T&&類型,而T必須被包含在函數模板的模板參數列表之中。按照轉發型引用類型形參的特點,該形參將根據所對應的實參的左右值屬性而分別蛻變成左右值引用。但無論該形參成為左值引用還是右值引用,該形參在函數模板內都將成為左值。這是因為該形參有名字,左值引用是左值,具名右值引用也同樣是左值。如果在函數模板內照原樣轉發該形參,其他函數就只能將轉發而來的參數視為左值,完美轉發任務將會失敗。

#include<iostream> using namespace std; struct X {}; void inner(const X&) {cout << "inner(const X&)" << endl;} void inner(X&&) {cout << "inner(X&&)" << endl;} template<typename T> void outer(T&& t) {inner(t);} int main() {   X a;   outer(a);   outer(X()); } //inner(const X&) //inner(const X&) std::forward()

要在函數模板中完成完美轉發轉發型引用類型形參的任務,我們必須在相應實參為左值,該形參成為左值引用時把它轉發成左值,在相應實參為右值,該形參成為右值引用時把它轉發成右值。此時我們需要標準庫函數std::forward()。

標準庫函數 std::forward<T>(t) 有兩個參數:模板參數 T 與 函數參數 t。函數功能如下:

  • 當T為左值引用類型U&時,t 將被轉換為無名左值引用(左值,類型為U&)。
  • 當T為非引用類型U或右值引用類型U&&時,t 將被轉換為無名右值引用(右值,類型為U&&)。

使用此函數,我們在函數模板中轉發類型為T&&的轉發型引用參數 t 時,只需將參數 t 替換為std::forward<T>(t)即可完成完美轉發任務。這是因為

  • 如果 t 對應的實參為左值(類型為U&),模板參數T將被推導為引用類型U&,t 成為具名左值引用(類型為U&),std::forward<T>(t)就會把 t 轉換成無名左值引用(左值,類型為U&)。
  • 如果 t 對應的實參為右值(類型為U&&),模板參數T將被推導為非引用類型U,t 成為具名右值引用(類型為U&&),std::forward<T>(t)就會把 t 轉換成無名右值引用(右值,類型為U&&)。
#include<iostream> using namespace std; struct X {}; void inner(const X&) {cout << "inner(const X&)" << endl;} void inner(X&&) {cout << "inner(X&&)" << endl;} template<typename T> void outer(T&& t) {inner(forward<T>(t));} int main() {   X a;   outer(a);   outer(X()); } //inner(const X&) //inner(X&&) 

總結

以上所述是小編給大家介紹的C++11右值引用和轉發型引用教程詳解,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對VEVB武林網網站的支持!


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
成人午夜在线视频一区| 国产精品久久久久久一区二区| 国产亚洲欧美日韩一区二区| 精品久久久香蕉免费精品视频| 国产精品精品一区二区三区午夜版| 久久久精品国产亚洲| 亚洲美女又黄又爽在线观看| 成人中心免费视频| 国产99视频在线观看| 国产精品久久9| 亚洲精品永久免费精品| 日韩成人在线视频| 亚洲国产成人精品久久久国产成人一区| 懂色av影视一区二区三区| 国产精品美女主播在线观看纯欲| 亚洲黄页视频免费观看| 麻豆乱码国产一区二区三区| 欧洲亚洲免费在线| 国产精品久久二区| 91精品国产色综合久久不卡98| 亚洲无限av看| 欧美国产欧美亚洲国产日韩mv天天看完整| 国产欧美精品一区二区三区-老狼| 5566成人精品视频免费| 久久91精品国产91久久跳| 国产中文日韩欧美| 欧美理论电影网| 国产日韩精品电影| 久久久久久国产精品三级玉女聊斋| 永久免费看mv网站入口亚洲| 久久免费国产视频| 欧美综合一区第一页| 欧美做受高潮电影o| 日韩专区在线观看| 最近2019中文字幕mv免费看| 91av在线播放视频| 91精品国产综合久久男男| 国产精品99久久久久久人| 久久免费视频这里只有精品| 色爱精品视频一区| 97色在线观看| 亚洲视频国产视频| 国产精品电影久久久久电影网| 琪琪第一精品导航| 久久在精品线影院精品国产| 国产精品爱啪在线线免费观看| 国产精品美女免费视频| 亚洲精品永久免费精品| 国产成人a亚洲精品| 91久久精品美女| 国产亚洲a∨片在线观看| 亚洲国产小视频| 亚洲黄色在线观看| 亚洲国产毛片完整版| 亚洲国产精品成人va在线观看| 欧美大片在线影院| 精品视频久久久久久久| 亚洲精品自产拍| 欧美精品video| 欧美激情日韩图片| 欧美激情精品久久久久久| 国产精品久久久久av免费| 欧美激情中文字幕在线| 色偷偷88888欧美精品久久久| 日韩久久午夜影院| 欧美亚洲在线播放| 欧美日韩一区二区在线播放| 亚洲xxxx18| 亚洲一区二区自拍| 久久久久久97| 日韩欧美国产一区二区| 国产经典一区二区| 国内精品久久久久久久| 日本精品在线视频| 国产精品香蕉在线观看| 91大神在线播放精品| 亚洲久久久久久久久久| 热久久这里只有| 日韩不卡中文字幕| 久久视频在线直播| 久久91精品国产91久久跳| 亚洲午夜性刺激影院| 亚洲**2019国产| 亚洲综合日韩在线| 国产玖玖精品视频| 欧美日韩国产黄| 亚洲第一免费播放区| 国产精品av电影| 亚洲精品自拍第一页| 精品无人区太爽高潮在线播放| 色综合色综合网色综合| 高跟丝袜一区二区三区| 川上优av一区二区线观看| 欧美精品午夜视频| 亚洲大尺度美女在线| 97成人在线视频| 国产欧美日韩精品在线观看| 国产成人在线一区| 97视频在线观看网址| 亚洲欧美国产精品专区久久| 伊人亚洲福利一区二区三区| 国产精品视频播放| 91国产精品视频在线| 中文字幕日韩高清| 国产精品视频地址| 国语自产精品视频在线看抢先版图片| 成人免费福利视频| 日韩精品免费电影| 亚洲国产精品国自产拍av秋霞| 欧美激情在线视频二区| 国产精品免费一区| 欧美性开放视频| 久久久久久久久久久国产| 国产xxx69麻豆国语对白| 久久精品国产96久久久香蕉| 亚洲欧洲av一区二区| 亚洲性xxxx| 黑人欧美xxxx| 91po在线观看91精品国产性色| 久久久久久久91| 久久久成人精品视频| 欧美激情欧美狂野欧美精品| 久久理论片午夜琪琪电影网| 亚洲第一区第一页| 中文字幕欧美国内| 亚洲欧美中文字幕| 亚洲伊人成综合成人网| 九九热在线精品视频| 欧美视频在线免费看| 久久电影一区二区| 国产视频欧美视频| 国产大片精品免费永久看nba| 久操成人在线视频| 精品中文字幕视频| 国产成人精品999| 97免费视频在线| 中文字幕国产亚洲| 精品露脸国产偷人在视频| 日韩一二三在线视频播| 琪琪第一精品导航| 成人黄色在线播放| 日韩a**站在线观看| 国产精品久久久久久久久久尿| 国产精品69精品一区二区三区| 欧美日韩不卡合集视频| 亚洲精品国产免费| 欧美综合国产精品久久丁香| 国产亚洲精品成人av久久ww| 日韩有码在线播放| 亚洲有声小说3d| 国产亚洲精品久久久久久777| 亚洲午夜精品久久久久久性色| 国产精品成人久久久久| 国产日韩欧美电影在线观看| 成人免费在线视频网址| 欧美专区福利在线| 亚洲四色影视在线观看| 97久久久久久| 欧美性猛xxx| 国产一区二区久久精品| 欧美一区二区.| 亚洲毛茸茸少妇高潮呻吟| 亚洲偷欧美偷国内偷| 日本一本a高清免费不卡|