日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

右值引用及其作用

發(fā)布時(shí)間:2024/3/26 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 右值引用及其作用 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

??????一、什么是左值和右值?

? ? ? ? 在掌握右值引用前,必須先知道什么是右值,既然有右值,那么肯定有左值。當(dāng)我們?cè)谫x值的時(shí)候a=b,能夠被放到=號(hào)左邊的值即為左值,反則為右值。

? ? ? ? 那么什么值可以作為左值呢?顯然變量肯定是可以作為左值的。什么值不能作為左值呢?顯然常數(shù)、表達(dá)式、函數(shù)返回值等,是不能作為左值的,也就是右值。顯然作為左值的都是可以長(zhǎng)期保存起來(lái)的,對(duì)應(yīng)是保存在內(nèi)存中;但常數(shù)、表達(dá)式、函數(shù)返回值等都是臨時(shí)值,這些值都保存在寄存器中。可以總結(jié):

  • 左值:可以長(zhǎng)時(shí)間保存,可以存在于=左邊的值,可以取地址;
  • 右值:臨時(shí)值,不能存在于=左邊的值,不可以取地址。
  • int a = 9, b = 8; a = 8, b = 0; // a,b為左值 // a + 4 = 5; // 錯(cuò)誤 a + 4為右值,a + 4為一個(gè)臨時(shí)對(duì)象 // -a = 4; // 錯(cuò)誤 -a為右值,為一個(gè)臨時(shí)對(duì)象 (a) = 5; // 正確 (a)為左值,返回的是a ++a = 3; // 正確 ++a為左值,可以理解為a先+1,然后返回a本身,也即整個(gè)表達(dá)是a // a++ = 3; // 錯(cuò)誤 a++為右值,可以理解為int tmp = a;a = a +1;return tmp;返回的是一個(gè)臨時(shí)變量

    二、什么是左值引用和右值引用?

    ? ? ? ? 對(duì)左值的引用即為左值引用,對(duì)右值的引用即為右值引用。需要注意得是引用是對(duì)值得別名,所以不會(huì)產(chǎn)生副本,同時(shí)引用本身也是變量,所以也是左值。如下:

    int &t = a; // a為左值,所以可以賦給左值引用 // int &t1 = 3; // 錯(cuò)誤 3為一個(gè)臨時(shí)值,為右值,不能賦給左值引用 // int &&t = a; // 錯(cuò)誤 a為左值,不能賦給右值引用 int &&t = 3; // 可以 int &&t = -a; // 可以 // int &t = -a; // 不可以 // int &&t1 = t; // 不可以,t本身是左值

    三、右值引用作用

    ? ? ? ? 右值引用是C++11新特性,之所以引入右值引用,是為了提高效率。如下面所示:

    class A { public:A(size_t N):m_p(new char[N]){}A(const A & a){if (this != &a){delete[]m_p;m_p = new char[strlen(m_p) + 1];memcpy(m_p, a.m_p, strlen(m_p) + 1);}}~A(){delete []m_p;}private:char *m_p = nullptr; };A createA(size_t N) {return A(100); }void func(A a) {// }int main() {func(createA(100));system("pause");return 0; }

    ?這里會(huì)導(dǎo)致大量得調(diào)用A得構(gòu)造函數(shù),不考慮編譯優(yōu)化,原本執(zhí)行如下:

  • createA(100),執(zhí)行A(100)調(diào)用A(size_t)構(gòu)造函數(shù)一次
  • 退出createA,臨時(shí)構(gòu)造得A(100),釋放調(diào)用析構(gòu)函數(shù)一次;
  • 賦給返回值會(huì)調(diào)用一次拷貝構(gòu)造函數(shù)一次;
  • 返回值傳入func中形參會(huì)調(diào)用拷貝構(gòu)造函數(shù)一次
  • func運(yùn)行完成后形參釋放,調(diào)用A析構(gòu)函數(shù)一次;
  • 返回值使用完成釋放,調(diào)用A析構(gòu)函數(shù)一次;
  • 從上面可以看出有大量得構(gòu)造、析構(gòu)調(diào)用 ,但是我們做的工作無(wú)非就是臨時(shí)構(gòu)造一個(gè)A(100)給func使用而已。那么可否將臨時(shí)A(100)始終一份給到func使用呢?答案就是右值引用。如下:

    class A { public:A(size_t N):m_p(new char[N]){}~A(){delete []m_p;}private:char *m_p = nullptr; };A&& createA(size_t N) {return (A&&)A(100); }void func(A&& a) {// }int main() {func(createA(100));system("pause");return 0; }

    ? ? ? ? 我們將臨時(shí)A(100)強(qiáng)制轉(zhuǎn)換為了右值引用,同時(shí)func形參也是右值引用,也就是將臨時(shí)對(duì)象延長(zhǎng)到了func中,中間避免了其他構(gòu)造和析構(gòu)調(diào)用,提高了效率。

    ? ? ? ? 注意到我們將A得拷貝構(gòu)造函數(shù)去掉了,因?yàn)橐呀?jīng)用不到。如果原版寫法,去掉拷貝構(gòu)造函數(shù)會(huì)崩潰,因?yàn)闀?huì)自動(dòng)調(diào)用默認(rèn)拷貝構(gòu)造函數(shù),是淺拷貝,中間臨時(shí)對(duì)象會(huì)提前刪除公共內(nèi)存,后面對(duì)象再次釋放是就會(huì)重復(fù)刪除內(nèi)存導(dǎo)致崩潰。

    四、將左值轉(zhuǎn)換為右值(std::move)

    ? ? ? ? 右值引用只能綁定右值,那是否可以將右值引用綁定到左值呢?可以,一個(gè)很簡(jiǎn)單的寫法就是強(qiáng)制轉(zhuǎn)換,如下:

    int main() {int a = 3;int &&t = (int &&)a;t = 9;cout << a << endl; // a = 9system("pause");return 0; }

    ? ? ? ? 其實(shí),C++11提供了更為優(yōu)雅的轉(zhuǎn)換函數(shù)std::move,std::move(a)無(wú)論a是左值還是右值都將轉(zhuǎn)換為右值。如下:

    int main() {int a = 3;int &&t = std::move(a);int &&t2 = std::move(3);system("pause");return 0; }

    五、std::move實(shí)現(xiàn)原理(強(qiáng)制轉(zhuǎn)換)

    ? ? ? ? 可以打開std::move源碼,如下:

    template<class _Ty> inlineconstexpr typename remove_reference<_Ty>::type&&move(_Ty&& _Arg) _NOEXCEPT{ // forward _Arg as movablereturn (static_cast<typename remove_reference<_Ty>::type&&>(_Arg));}

    可以發(fā)現(xiàn),move是將傳入的_Arg值,強(qiáng)制轉(zhuǎn)換為了typename remove_reference<_Ty>::type&&類型,那么typename remove_reference<_Ty>::type是什么呢?接著往下看。

    template<class _Ty>struct remove_reference{ // remove referencetypedef _Ty type;};template<class _Ty>struct remove_reference<_Ty&>{ // remove referencetypedef _Ty type;};template<class _Ty>struct remove_reference<_Ty&&>{ // remove rvalue referencetypedef _Ty type;};

    原來(lái),remove_reference是一個(gè)類模板。第一個(gè)type類型為傳入類型本身;第二個(gè)類模板形參是左值引用,type為去掉的引用類型;第三個(gè)類模板形參為右值引用,type為去掉引用的類型。從本身字面意思也可知道remove_reference的作用就是去掉引用得到原本的類型。

    ? ? ? ? 我們?cè)俅位氐絤ove。move返回的是typename remove_reference<_Ty>::type&&,原本類型的右值引用。至此move作用就非常清晰了:就是將傳入值強(qiáng)制轉(zhuǎn)換為值原類型的右值引用。

    ?六、通用引用及其條件

    ? ? ? ? 如果你足夠仔細(xì)的話,你會(huì)發(fā)現(xiàn)move形參為_Ty&&,形式上是右值引用,那為什么傳入左值不會(huì)發(fā)生錯(cuò)誤呢?這涉及到通用引用,所謂通用引用就是根據(jù)接受值類型可以自行推導(dǎo)是左值引用還是右值引用。

    ? ? ? ? 對(duì)于形如:

    template<typename T> void print(T &&) {}

    ? ? ? ??如果傳入?yún)?shù)是左值則會(huì)被推導(dǎo)為print(int&),如果參數(shù)是右值則推導(dǎo)為print(int&&),除了模板外auto &&也有相同效果,如下:

    template<typename T> void print(T &&) {}int main() {int a = 9;print(a); // print(int&)print(8); // print(int&)auto &&t = 3; // int &&auto &&t2 = a; // int &system("pause");return 0; }

    ? ? ? ? ?那是不是所有模板函數(shù)形參T&&都是通用引用呢?答案是否定的。條件是:如果聲明變量或參數(shù)具有T&&某種推導(dǎo)類型的類型?T,則該變量或參數(shù)為通用引用,否則就是右值引用(無(wú)法傳入左值)。

    ? ? ? ? 也就是傳入的參數(shù)在編譯時(shí)需要推導(dǎo),如果不需要推導(dǎo),則不是通用引用。如下:

    template<typename T> class B { public:void print(T &&) {} };int main() {B<int> b;b.print(3); // 為右值引用system("pause");return 0; }

    因?yàn)樵诰幾gprint之前print中的參數(shù)已經(jīng)由B<int> b確定了,所以在print編譯時(shí)無(wú)需推導(dǎo),故B中的T&&為右值引用。如果改為如下:

    template<typename T> class B { public:template<typename Arg>void print(Arg &&) {} };int main() {B<int> b;b.print(3); // 為右值引用system("pause");return 0; }

    ? ? ? ? 因?yàn)閜rint時(shí)函數(shù)模板形參和類模板形參類型時(shí)獨(dú)立的,故在編譯print時(shí)是需要推導(dǎo)的,故Arg&&為通用引用。同理,下面的int&&也不是通用引用,因?yàn)轭愋鸵呀?jīng)確定。

    template<typename T> class B { public:void print(int &&) {} };int main() {B<char> b;int t = 0;// b.print(t); // 出錯(cuò),print(int&&)為右值引用system("pause");return 0; }

    ?????????通用引用的形式必須是T&&,添加其他修飾都不是通用引用,如下:

    template<typename T> void print(const T&&) // 右值引用 { }template<typename T> void print(std::vector<T>&& params) // 右值引用 { }

    七、引用折疊?

    ? ? ? ??上面我們知道通用引用雖然形式上是右值引用,但是卻可以接受左值,這是怎么實(shí)現(xiàn)的呢?這就是引用折疊。

    有如下代碼:

    template<typename T> void print(T&& t) { }int main() {int a = 9;print(a);print(9);system("pause");return 0; }

    ?????????print(a)時(shí),因?yàn)閍為左值,會(huì)被推導(dǎo)成print(int& &&t)形式,int& &&t 會(huì)被折疊為int &,所以最終形式為print(int &)。(左值被推導(dǎo)為左值引用)

    ? ? ? ? print(9)時(shí),為9為右值,所以被推導(dǎo)為print(int&& &&)形式,而int&& &&會(huì)被折疊為int&&,所以最終形式為print(int&&)。(右值被推導(dǎo)為右值引用)

    ? ? ? ? 當(dāng)將引用本身

    ? ? ? ? 引用類型只有兩種,所以折疊形式就是4中,為:T& &,T& &&,T&& &,T&& &&。引用折疊規(guī)則概況為兩種:

    • T&& &&折疊為T&&;
    • 其他折疊為T&.

    ? ? ? ? 下面使用typedef驗(yàn)證引用折疊,如下:

    template<typename T> class B { public:typedef T& type;typedef T&& type2; };int main() {int a = 9;B<int>::type t = a; // type->int&B<int&>::type t2 = a; // type->int& &折疊為int&B<int&&>::type t3 = a; // type->int&& &折疊為int&B<int>::type2 t4 = 3; // type2->int&&B<int&>::type2 t5 = a; // type2->int& &&折疊為int&B<int&&>::type2 t6 = 3; // type2->int&& &&折疊為int&&system("pause");return 0; }

    ?八、完美轉(zhuǎn)發(fā)及其意義

    ? ? ? ? 通用引用既可以接受左值也可以接受右值,但是通用引用本身是左值。如果在函數(shù)模板中繼續(xù)傳遞該值給其他函數(shù),勢(shì)必會(huì)改變?cè)撝档膶傩?#xff0c;即都為左值引用。如下:

    template<typename T> void _print(T &&t) {cout << (std::is_lvalue_reference<decltype(t)>::value ? "lvalue" : "rvalue") << endl; }template<typename T> void print(T&& t) {_print(t); }int main() {int a = 3;print(a); // lvalueprint(3); // lvaluesystem("pause");return 0; }

    本來(lái)3為右值,傳遞到_print之后變成了左值。整個(gè)屬性是被print改變的。那么可否有一種方式以原屬性傳遞呢?答案是std::foward,被稱為完美轉(zhuǎn)發(fā)。如下代碼:

    template<typename T> void _print(T &&t) {cout << (std::is_lvalue_reference<decltype(t)>::value ? "lvalue" : "rvalue") << endl; }template<typename T> void print(T&& t) {_print(std::forward<T>(t)); }int main() {int a = 3;print(a); // lvalueprint(3); // rvaluesystem("pause");return 0; }

    ?在print中傳遞給_print的值為std::foward<T>(t),傳遞給_print,接受的值屬性和之前保持一致。所謂完美轉(zhuǎn)發(fā)就是不改變值原本屬性進(jìn)行轉(zhuǎn)發(fā)。

    ? ? ? ? 完美轉(zhuǎn)發(fā)有什么意義呢?某個(gè)功能對(duì)左值和右值處理情況不一致,如果將左值和右值引用當(dāng)作同一種情況使用,可能會(huì)會(huì)有性能損失。假設(shè)有如下代碼:

    class A { public:A() {}A(int a) { m_pa = new int(a); }A(const A &a) {if (this != &a){delete m_pa;m_pa = new int(*a.m_pa);}}~A() { delete m_pa; }int *m_pa = nullptr; };A * _makeA(A &a) {return new A(a); }A * _makeA(A &&a) {A *pa = new A;pa->m_pa = a.m_pa;a.m_pa = nullptr;return pa; }template<typename T> auto makeA(T&& t) {return _makeA(t); }int main() {A a(3);auto t = makeA(a);auto t2 = makeA(A(4));system("pause");return 0; }

    ? ? ? ? 該代碼作用是根據(jù)已有的A對(duì)象創(chuàng)建新的A對(duì)象指針。因?yàn)樽笾稻哂醒訒r(shí)性,所以根據(jù)左值創(chuàng)建A指針時(shí)是將對(duì)象中m_pa進(jìn)行了深拷貝;根據(jù)臨時(shí)對(duì)象創(chuàng)建A指針時(shí),由于臨時(shí)對(duì)象由于已經(jīng)創(chuàng)建好了m_pa,沒有必要再創(chuàng)建將臨時(shí)對(duì)象的m_pa進(jìn)行深拷貝,只需要將臨時(shí)對(duì)象的m_pa給到新創(chuàng)建的A即可,同時(shí)將臨時(shí)對(duì)象的m_pa指向nullptr。這樣可以提高性能。

    ? ? ? ? 但是通過(guò)makeA間接傳遞給_makeA之后,都調(diào)用了_makeA(A&),也就是對(duì)m_pa進(jìn)行了深拷貝,這與原本make(A(4))想調(diào)用_makeA(A&&)不一致。

    ? ? ? ? 如果我們能在makeA中完美轉(zhuǎn)發(fā)t,那么就可以達(dá)到要求,這就是std::foward的意義。

    九、std::foward原理

    ? ? ? ?先看下std::foward的實(shí)現(xiàn)代碼,如下:

    template<class _Ty> inlineconstexpr _Ty&& forward(typename remove_reference<_Ty>::type& _Arg) _NOEXCEPT{ // forward an lvalue as either an lvalue or an rvaluereturn (static_cast<_Ty&&>(_Arg));}template<class _Ty> inlineconstexpr _Ty&& forward(typename remove_reference<_Ty>::type&& _Arg) _NOEXCEPT{ // forward an rvalue as an rvaluestatic_assert(!is_lvalue_reference<_Ty>::value, "bad forward call");return (static_cast<_Ty&&>(_Arg));}

    我們簡(jiǎn)化為_foward版本(方便看)?,如下:

    template<typename T> T&& _forward(typename remove_reference<T>::type& t) {return static_cast<T&&>(t); }template<typename T> T&& _forward(typename remove_reference<T>::type&& t) {return static_cast<T&&>(t); }

    foward給出了兩個(gè)版本,一個(gè)接收左值,一個(gè)接收右值。現(xiàn)在我們使用_foward,如下:

    void _print(int &t) {cout << "lvalue" << endl; }void _print(int &&t) {cout << "rvalue" << endl; }template<typename T> void print(T &&t) {_print(_forward<T>(t)); }

    ?????????這里有兩個(gè)疑問(wèn):1)如果我們調(diào)用print(3),_foward會(huì)執(zhí)行哪個(gè)版本?2)如果我們調(diào)用print(a),因?yàn)開foward中強(qiáng)制轉(zhuǎn)換為了T&&, 是否都調(diào)用_print(int&&)版本?

    ? ? ? ? 問(wèn)題1:無(wú)論print的參數(shù)是左值還是右值,傳遞給print后t都為左值,所以_foward會(huì)執(zhí)行左值版本,即第一個(gè)版本。

    ? ? ? ? 問(wèn)題2:在_foward都將t強(qiáng)制轉(zhuǎn)換為了T&&,按照道理來(lái)說(shuō),應(yīng)該都會(huì)執(zhí)行_print(int&&),不信可以執(zhí)行:

    ????????int a = 3;

    ????????_print((int&&)(a));

    代碼,發(fā)現(xiàn)確實(shí)是調(diào)用了_print(int&&)。既然這樣_foward為什么還能做到完美轉(zhuǎn)發(fā)呢?顯然通過(guò)_foward(a)之后的調(diào)用的肯定是_print(int&)版本。在_foward中使用static_cast<T&&>(或者T&&)和int&&結(jié)果不一樣呢?原因是引用折疊。具有推導(dǎo)類型的T&&轉(zhuǎn)換會(huì)進(jìn)行引用折疊。而int&&類型是確定的,不能進(jìn)行折疊。

    ? ? ? ? 如果調(diào)用print(3),傳遞給_foward的t為&&類型,然后T&& &&將折疊為T&&,故最終會(huì)調(diào)用_print(int&&)版本;如果調(diào)用print(a),傳遞給_foward的t為&類型然后T&& &將折疊為T&,所以最終調(diào)用_print(int&)版本。

    參考:

    https://covariant.cn/2020/02/25/uniref-in-cpp/

    https://avdancedu.com/a39d51f9/

    https://blog.csdn.net/weixin_40539125/article/details/89578720?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control

    ????????

    總結(jié)

    以上是生活随笔為你收集整理的右值引用及其作用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。