1. <ul id="0c1fb"></ul>

      <noscript id="0c1fb"><video id="0c1fb"></video></noscript>
      <noscript id="0c1fb"><listing id="0c1fb"><thead id="0c1fb"></thead></listing></noscript>

      99热在线精品一区二区三区_国产伦精品一区二区三区女破破_亚洲一区二区三区无码_精品国产欧美日韩另类一区

      RELATEED CONSULTING
      相關咨詢
      選擇下列產(chǎn)品馬上在線溝通
      服務時間:8:30-17:00
      你可能遇到了下面的問題
      關閉右側工具欄

      新聞中心

      這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
      【C++11】三大神器之——右值、移動語義、完美轉發(fā)-創(chuàng)新互聯(lián)
      前言

      如果你還不知道C++11引入的右值、移動語義、完美轉發(fā)是什么,可以閱讀這篇文章;如果你已經(jīng)對這些知識了如指掌,也可以看看有什么可以補充~😏

      創(chuàng)新互聯(lián)公司2013年成立,是專業(yè)互聯(lián)網(wǎng)技術服務公司,擁有項目成都網(wǎng)站制作、網(wǎng)站建設、外貿(mào)網(wǎng)站建設網(wǎng)站策劃,項目實施與項目整合能力。我們以讓每一個夢想脫穎而出為使命,1280元新樂做網(wǎng)站,已為上家服務,為新樂各地企業(yè)和個人服務,聯(lián)系電話:18982081108一、右值 值類別vs變量類型

      在正式認識右值之前,我們要先區(qū)分值的類別和變量類型:

      • 值 (value)變量 (variable)是兩個獨立的概念。值不一定擁有變量名(如表達式:i + j + k)。
      • 值只有類別(category)之分,而變量只有類型(type)之分。

      值類別可以被劃分左值右值

      那什么是左值和右值呢?左值是能被取地址、不能被移動的值。右值是表達式中間結果/函數(shù)返回值(可能擁有變量名,也可能沒有)。

      有一個可以區(qū)分左值和右值的便捷方法:看能不能對表達式取地址,如果能,則為左值,否則為右值。

      C++11擴展了右值的概念,將右值分為了純右值和將亡值,但本文不作討論。

      如下的示例將幫助我們區(qū)分左值和右值:

      int i = 3;       // i是左值,3是右值
      int j = i+8;    // j是左值,i+8是右值
      char a = getCh();   // a是左值 ,getCh()的返回值是右值(臨時變量)
      左值引用、右值引用、常引用

      在以前的文章中,我們曾經(jīng)討論過左值引用和常引用的區(qū)別。在本篇文章中,我們需要進一步系統(tǒng)的了解它們?nèi)咧g的關系。

      引用類型 可以分為兩種:

      • 左值引用:用&符號引用左值(但不能引用右值),
      • 右值引用:用&&符號引用右值(可以移動左值)。

      在C++11中,因為增加了右值引用(rvalue reference)的概念,所以C++98中的引用都稱為了左值引用(lvalue reference)。

      使用方法如下所示:

      int&& a = 3;         // 3是右值,a是右值引用
      int b = 8;               // b是左值
      int& bb = b;			//bb是左值引用
      int&& c = b + 5;   //  b+5是右值,c是右值引用
      AA&& aa = getTemp();   // getTemp()的返回值是右值(臨時變量)

      左值引用十分常見,我們知道是給變量取個別名,但是引入右值引用的意義是什么呢?(將在下文中解答)

      在上述的代碼中,getTemp()的返回值本來在表達式語句結束后,其生命也就該終結了(因為是臨時變量),而通過右值引用重獲了新生,其生命周期將與右值引用類型變量aa的生命周期一樣,只要aa還活著,該右值臨時變量將會一直存活下去。

      在下面的代碼中將幫助我們區(qū)分左值引用和右值引用:

      void func(T& a);//1,參數(shù)是左值
      void func(T&& a);//2,參數(shù)是右值
      //T類型的變量
      T var;
      T& rvar1 = var;//正確,rvar1是左值
      T& rvar1 = T{};//錯誤,左值引用不能引用右值
      T&& rvar2 = T{};//正確,rvar2是右值
      T&& rvar2 = var;//錯誤,右值引用不能引用左值
      T&& rvar2 = std::move(var);//正確,可以通過std::move()將左值轉為右值引用
      
      func(var);//進入1,a是左值
      func(T{});//進入2,a是右值
      func(rvar1);//進入1,a是左值
      func(rvar2);//進入1,rvar2是右值引用但a是左值

      可以看出:

      • 當左值引用變量rvar1在初始化時,不能綁定右值T{}
      • 當右值引用變量rvar2在初始化時,不能綁定左值var,但是可以通過std::move()將左值轉為右值引用。
      • 在代碼的最后,右值引用變量rvar2作為實參傳入func中時,在作用域內(nèi)是左值(已命名的右值引用是左值)。

      另外,C++還支持了常引用,能夠同時接受左值和右值(作為常引用)。

      void func(const T& a);//a是常引用

      常引用和右值引用 都能接受右值的綁定,有什么區(qū)別呢?

      • 常引用可以像右值引用一樣將右值的生命期延長,但它有一個缺點是,只能讀不能改。

      現(xiàn)在回到我們的問題:引入右值引用的意義是什么?

      如果函數(shù)重載能夠同時接受:右值引用/常引用參數(shù),則編譯器將優(yōu)先重載:右值引用參數(shù),即引入右值引用的主要目的是實現(xiàn)移動語義。

      下面是不同值作為實參傳入形參時,函數(shù)重載優(yōu)先級(數(shù)字越小優(yōu)先級越高):

      實參/形參T&const T&T&&const T&&
      左值12
      常左值1
      右值312
      常右值21
      引用折疊

      在正式學習移動語義(move semantic)完美轉發(fā)std::forward()之前,我們還要提一嘴引用折疊(reference collapsing),它是移動語義和完美轉發(fā)的實現(xiàn)基礎。

      using Lref = Data&;
      using Rref = Data&&;
      Data data;
      
      Lref&  r1 = data;    // r1是左值
      Lref&& r2 = data;    // r2是左值
      Rref&  r3 = data;    // r3是左值
      Rref&& r4 = Data{};  // r4是右值

      總之,只有右值引用折疊到右值引用上仍然是一個右值引用,而其他所有的引用類型之間的折疊都將變成左值引用。

      二、移動語義 為什么需要移動語義

      我們知道,如果一個對象中有堆區(qū)資源,需要編寫拷貝構造函數(shù)和賦值函數(shù),實現(xiàn)深拷貝。如果被拷貝的對象是臨時的,拷貝完就沒什么用了,這樣會造成沒有意義的資源申請和釋放操作。如果能將對象包含的資源,直接從舊對象移動到新對象,就可以節(jié)省資源申請和釋放的時間。C++11新增加的移動語義(move semantic)就是為了做到這一點(基本類型不包含資源,其移動和拷貝相同。)。

      還有另一種情況:如果資源對象本身不可拷貝(如智能指針std::unique_ptr)需要定義移動構造/移動賦值函數(shù),其原理類似。

      實現(xiàn)移動語義要增加兩個函數(shù):移動構造函數(shù)和移動賦值函數(shù)。我們通過實現(xiàn)一個簡單的string類對象來說明:

      class String
      {public:
          String()
          {cout<< "String類"<< this<< "的構造函數(shù)"<< endl;// 顯示自己被調(diào)用的日志
              const char* s = "Hello C++";
              int len = strlen(s);
              _str = new char[len + 1];
              strcpy(_str, s);
          }
      
          String(const String& another)
          {cout<< "String類"<< this<< "的拷貝構造"<< endl;// 顯示自己被調(diào)用的日志
              int len = strlen(another._str);//獲取源對象中字符串的長度
              _str = new char[len + 1];
              strcpy(_str, another._str);// 把數(shù)據(jù)從源對象中拷貝過來
          }
      
          String& operator=(String& another)
          {cout<< "String類"<< this<< "的拷貝賦值"<< endl;// 顯示自己被調(diào)用的日志
              if (this == &another)
                  return *this;// 避免自我賦值
              int len = strlen(another._str);//獲取源對象中字符串的長度
              _str = new char[len + 1];
              strcpy(_str, another._str);// 把數(shù)據(jù)從源對象中拷貝過來
              return *this;
          }
      
          String(String&& another)noexcept
          {cout<< "String類"<< this<< "的移動構造"<< endl;// 顯示自己被調(diào)用的日志
              if(_str != nullptr)
                  delete[] _str;  //如果已分配內(nèi)存,先釋放掉
              this->_str = another._str;// 把資源從源對象中轉移過來
              another._str = nullptr;// 把源對象中的指針置空
          }
      
          String& operator=(String&& another)
          {cout<< "String類"<< this<< "的移動賦值"<< endl;// 顯示自己被調(diào)用的日志
              if (this == &another)
                  return *this;// 避免自我賦值
              if(_str != nullptr)
                  delete[] _str; //如果已分配內(nèi)存,先釋放掉
              this->_str = another._str;// 把資源從源對象中轉移過來
              another._str = nullptr;// 把源對象中的指針置空
              return *this;
          }
      
          friend ostream& operator<<(ostream& out, String& str)
          {out<< str._str;
              return out;
          }
      
          ~String()
          {cout<< "String類"<< this<< "的析構函數(shù)"<< endl;// 顯示自己被調(diào)用的日志
              if(_str != nullptr)
              {delete[] _str;
                  _str = nullptr;
              }
          }
      
      private:
          char* _str;
      };

      上述代碼中,編譯器會根據(jù)傳入的實參的優(yōu)先級(詳見上文表格),來決定重載的構造函數(shù)。

      • 當實參是左值時,使用拷貝構造,拷貝源對象所有的元素。
      • 當實參是右值時,使用移動構造,將指向源對象的內(nèi)存空間的指針“移動”到新對象,并將源對象的指針置空。
      • 拷貝/移動賦值函數(shù)的原理相同,在此不再過多描述。

      測試用例及輸出的結果:

      int main()
      {{String str1{};//String類0x751f9ffd60的構造函數(shù)
              cout<< "str1 = "<< str1<< endl;//str1 = Hello C++
      
              String str2{str1};//String類0x751f9ffd58的拷貝構造
              cout<< "str2 = "<< str2<< endl;//str2 = Hello C++
      		
      		//返回一個右值(臨時對象)的lambda函數(shù)
              auto f = [] {String aa; return aa;}; 
      
              // String str3 = f();//拷貝省略
              // cout<< "str3 = "<< str3<< endl;
      
              String str{};//String類0x751f9ffd48的構造函數(shù)
              String str3{move(str)};//String類0x751f9ffd40的移動構造
              cout<< "str3 = "<< str3<< endl;//str3 = Hello C++
      
              String str4{};//String類0x751f9ffd38的構造函數(shù)
      		//String類0x751f9ffd68的構造函數(shù)
              str4 = f();//String類0x751f9ffd38的移動賦值
              //String類0x751f9ffd68的析構函數(shù)
              cout<< "str4 = "<< str4<< endl;//str4 = Hello C++
          }
          //String類0x751f9ffd38的析構函數(shù)
          //String類0x751f9ffd40的析構函數(shù)
          //String類0x751f9ffd48的析構函數(shù)
          //String類0x751f9ffd58的析構函數(shù)
          //String類0x751f9ffd60的析構函數(shù)
          system("pause");
          return 0;
      }

      盡管C++11引入了移動語義,但是仍有優(yōu)化的空間——與其調(diào)用一次沒有意義的移動構造函數(shù),不如讓編譯器直接跳過這個過程——于是就有了拷貝省略(copy elision)

      移動語義和拷貝省略的區(qū)別:

      • 移動語義是語言標準提出的概念。是通過編寫遵守移動語義的移動構造函數(shù)、右值限定成員函數(shù),在邏輯上優(yōu)化對象內(nèi)資源的轉移流程。
      • 拷貝省略是非標準(C++ 17 前)的編譯器優(yōu)化。跳過移動/拷貝構造函數(shù),讓編譯器直接在移動后的對象內(nèi)存上,構造被移動的對象。

      由于拷貝省略的存在,在上述代碼中,String str3 = f();會被編譯器優(yōu)化,為了方便演示移動構造函數(shù),我們使用了std::move()的方法移動返回值,當然這會造成不必要的開銷。

      三、完美轉發(fā)

      閱讀本節(jié)需要讀者有一定的模板編程基礎。

      通用引用

      C++11中引入了變長模板的概念,允許向模板參數(shù)里傳入不同類型的不定長引用參數(shù)。由于每個類型可能是左值引用或右值引用,針對所有可能的左右值引用組合,特化所有模板 是不現(xiàn)實的。

      如果沒用通用引用的概念,那么對于一個變長模板函數(shù),至少需要兩個重載:

      templatevoid func(T& arg, Args&...args)
      {func(args...);//左值直接展開
      }
      
      templatevoid func(T&& arg, Args&&...args)
      {func(std::move(args...));//右值需要std::move()轉發(fā)
      }

      Scott Meyers(Effective Modern C++的作者)指出“有時候符號&&并不一定代表右值引用,它也可能是左值引用。”

      事實上,如果一個引用符號需要通過推導才能得出左右值的類型(如模板參數(shù)類型或者auto),那么這個符號就可以是左值引用或右值引用——這就是通用引用 (universal reference)

      完美轉發(fā)

      這一點我們通過上文中的引用折疊的示例也可以得出。

      基于通用引用,我們可以對上述的代碼進行改進:

      templatevoid func(T&& arg, Args&&...args)
      {func(std::forward(args)... );
      }

      其中std::forward實現(xiàn)了針對左右值的參數(shù),能保證被轉發(fā)參數(shù)的左、右值屬性不變,即完美轉發(fā)(perfect forwarding)

      在C++11中,完美轉發(fā)支持:

      • 如果模板中(包括類模板和函數(shù)模板)函數(shù)的參數(shù)書寫成為T&& 參數(shù)名,那么,函數(shù)既可以接受左值引用,又可以接受右值引用。
      • 提供了模板函數(shù)std::forward(參數(shù)),用于轉發(fā)參數(shù),如果 參數(shù)是一個右值,轉發(fā)之后仍是右值引用;如果參數(shù)是一個左值,轉發(fā)之后仍是左值引用。

      使用示例如下:

      void fun1(int& i)//如果參數(shù)是左值
      {cout<< "左值 = "<< i<< endl;
      }
      
      void fun1(int&& i)//如果參數(shù)是右值
      {cout<< "右值 = "<< i<< endl;
      }
      templatevoid func(T&& ii)
      {fun1(std::forward(ii));
      }

      完美轉發(fā)的語法比較簡單,至于其原理本文暫時不深入研究。😝

      最后

      本文部分參考自文章

      你是否還在尋找穩(wěn)定的海外服務器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準確流量調(diào)度確保服務器高可用性,企業(yè)級服務器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧


      網(wǎng)頁名稱:【C++11】三大神器之——右值、移動語義、完美轉發(fā)-創(chuàng)新互聯(lián)
      網(wǎng)頁URL:http://www.ef60e0e.cn/article/cdessi.html
      99热在线精品一区二区三区_国产伦精品一区二区三区女破破_亚洲一区二区三区无码_精品国产欧美日韩另类一区
      1. <ul id="0c1fb"></ul>

        <noscript id="0c1fb"><video id="0c1fb"></video></noscript>
        <noscript id="0c1fb"><listing id="0c1fb"><thead id="0c1fb"></thead></listing></noscript>

        阿瓦提县| 长子县| 玉田县| 库尔勒市| 佛坪县| 阿克苏市| 区。| 苍梧县| 聂拉木县| 旬阳县| 白玉县| 北碚区| 沐川县| 武定县| 德安县| 鹤庆县| 德兴市| 毕节市| 泰来县| 西贡区| 昭苏县| 屏南县| 繁峙县| 平潭县| 新河县| 镇平县| 拉萨市| 大竹县| 隆德县| 齐齐哈尔市| 拜泉县| 襄垣县| 个旧市| 高邑县| 兴仁县| 安新县| 肃宁县| 阳江市| 黄冈市| 三江| 正定县|