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

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

生活随笔

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

编程问答

从自定义string类型理解右值引用

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

理解右值引用

    • 前言
    • 問(wèn)題復(fù)現(xiàn)
    • 自定義string(CMyString)
    • 遇到問(wèn)題
    • 圖示理解
    • 右值引用
      • 什么是右值
      • 添加右值引用參數(shù)的成員方法
    • 結(jié)果對(duì)比
    • 解決遺留問(wèn)題

前言

在之前,我寫(xiě)過(guò)一篇:
通過(guò)自定義string類(lèi)型來(lái)理解運(yùn)算符重載。
在文章末尾,我提出了一個(gè)問(wèn)題:
那么如何能夠?qū)崿F(xiàn)不泄露內(nèi)存,并且提高效率呢?
那么,今天就來(lái)看看如何解決這個(gè)問(wèn)題。

問(wèn)題復(fù)現(xiàn)

自定義string(CMyString)

首先是對(duì)string類(lèi)型的一個(gè)簡(jiǎn)單實(shí)現(xiàn),代碼如下:

class CMyString { public:CMyString(const char* str = nullptr){cout << "CMyString(const char*)" << endl;if (str != nullptr){_pstr = new char[strlen(str) + 1];strcpy(_pstr, str);}else{_pstr = new char[1];*_pstr = '\0';}}~CMyString(){cout << "~CMyString" << endl;delete[] _pstr;_pstr = nullptr;}CMyString(const CMyString& str){cout << "CMyString(const CMyString&)" << endl;_pstr = new char[strlen(str._pstr) + 1];strcpy(_pstr, str._pstr);}CMyString& operator=(const CMyString& src){cout << "operator=(const CMyString&)" << endl;if (this == &src)return *this;delete[]_pstr;_pstr = new char[strlen(src._pstr) + 1];strcpy(_pstr, src._pstr);return *this;}const char* c_str()const { return _pstr; } private:char* _pstr; }; };

注意:
在vs2017、vs2019這樣版本較新的編譯器,使用strcpy()函數(shù)一般都會(huì)報(bào)錯(cuò):

對(duì)于vs2017來(lái)說(shuō),可以直接在項(xiàng)目->屬性:

C/C++ -> 常規(guī) -> SDL檢查 -> 改為否

之后點(diǎn)擊應(yīng)用 -> 確定就好了。
但是對(duì)于vs2019來(lái)說(shuō),上述方法并不起作用:
博主經(jīng)過(guò)實(shí)踐,發(fā)現(xiàn)可以用如下方法解決:
在文件開(kāi)始添加宏定義:
#pragma warning(disable:4996)

這樣,就可以使用strcpy()了!

遇到問(wèn)題

之前的博客提到過(guò),對(duì)于正常的邏輯實(shí)現(xiàn),這個(gè)代碼是沒(méi)有問(wèn)題的,但是涉及到對(duì)象的優(yōu)化,
(可以移步到我的這篇博客:C++對(duì)象優(yōu)化)
或者換句話(huà)說(shuō),涉及到提高代碼的效率,就會(huì)出現(xiàn)問(wèn)題:
同樣地,我們使用如下代碼:

CMyString GetString(CMyString& str) {const char* pstr = str.c_str();CMyString tmpstr(pstr);return tmpstr; }int main() {CMyString str1("aaaaaaaaaaaaaaaaaaaaaaaaa");CMyString str2;str2 = GetString(str1);cout << str2.c_str() << endl;return 0; }

運(yùn)行結(jié)果如下:

圖示理解

可能直接從代碼運(yùn)行結(jié)果來(lái)看并不太直觀,我們來(lái)畫(huà)圖分析:

解讀:
首先第一步、第二步很好理解,都是調(diào)用了構(gòu)造函數(shù)進(jìn)行對(duì)象的構(gòu)造;
接著實(shí)參到形參因?yàn)槭褂靡脗鬟f,所以不會(huì)產(chǎn)生新對(duì)象;
進(jìn)入GetString()函數(shù),首先構(gòu)造局部對(duì)象tmpstr;
接著就執(zhí)行return語(yǔ)句,由于局部對(duì)象不能帶出局部作用域,所以為了能夠帶出局部對(duì)象的值,需要在主函數(shù)棧幀上構(gòu)建一個(gè)臨時(shí)對(duì)象,從局部對(duì)象到臨時(shí)對(duì)象調(diào)用了拷貝構(gòu)造函數(shù);
關(guān)鍵點(diǎn):
這個(gè)時(shí)候我們的代碼的邏輯是:
只要執(zhí)行拷貝構(gòu)造函數(shù),就會(huì)依據(jù)原對(duì)象的尺寸大小來(lái)開(kāi)辟一個(gè)新的空間,然后將原來(lái)的數(shù)據(jù)依次拷貝(strcpy)進(jìn)新的對(duì)象空間。
這樣的邏輯本身沒(méi)有什么問(wèn)題,但是一般自定義的類(lèi)型都會(huì)在堆上開(kāi)辟一定的空間,并且,這個(gè)空間中的數(shù)據(jù)可能很多。
執(zhí)行一次拷貝構(gòu)造,可能需要大量的內(nèi)存開(kāi)辟和數(shù)據(jù)復(fù)制。
最關(guān)鍵的,規(guī)模如此龐大的內(nèi)存開(kāi)辟和數(shù)據(jù)復(fù)制完成后,原來(lái)的對(duì)象就析構(gòu)了!
那你早說(shuō)啊!你把你的資源直接給我不就好了?
浪費(fèi)這么多感情和精力干嘛?

同理,在主函數(shù)的棧幀上構(gòu)建的臨時(shí)對(duì)象給str2進(jìn)行賦值的時(shí)候,也同樣需要大量的內(nèi)存開(kāi)辟和數(shù)據(jù)復(fù)制,拷貝完成后,臨時(shí)對(duì)象也就析構(gòu)掉了。

所以,對(duì)于這種涉及到臨時(shí)對(duì)象的構(gòu)造和賦值時(shí),我們不能使用常規(guī)的邏輯:
將我的資源復(fù)制一份給你;
而應(yīng)該轉(zhuǎn)換邏輯:
將我的資源轉(zhuǎn)移給你(因?yàn)榉凑乙膊挥昧思磳⑽鰳?gòu)掉)
這樣的話(huà),代碼的效率會(huì)有很大的提升!

右值引用

那么如何解決上述的問(wèn)題呢?
其實(shí)通過(guò)分析我們已經(jīng)知道,問(wèn)題就出在臨時(shí)對(duì)象上面。因?yàn)槟谜5倪壿嬋?duì)臨時(shí)對(duì)象操作,會(huì)有大量資源的消耗。
為了解決這個(gè)問(wèn)題,我們需要區(qū)分普通對(duì)象和臨時(shí)對(duì)象;
那么體現(xiàn)在自定義類(lèi)型中就是左值引用和右值引用的區(qū)別了。
我們可以首先來(lái)看看什么是右值。

什么是右值

要理解右值,可以從左值開(kāi)始。
百度百科對(duì)于左值和右值的概念為:

左值(lvalue)和右值(rvalue)最先來(lái)源于編譯。在C語(yǔ)言中表示位于賦值運(yùn)算符兩側(cè)的兩個(gè)值,左邊的就叫左值,右邊的就叫右值。
左值:指的是如果一個(gè)表達(dá)式可以引用到某一個(gè)對(duì)象,并且這個(gè)對(duì)象是一塊內(nèi)存空間且可以被檢查和存儲(chǔ),那么這個(gè)表達(dá)式就可以作為一個(gè)左值。
右值:指的是引用了一個(gè)存儲(chǔ)在某個(gè)內(nèi)存地址里的“數(shù)據(jù)”。

從上數(shù)定義我們可以簡(jiǎn)單概括為:
左值:有內(nèi)存、有名字;
右值:沒(méi)內(nèi)存或者沒(méi)名字(臨時(shí)量)。

添加右值引用參數(shù)的成員方法

所以我們上面說(shuō)的臨時(shí)對(duì)象就是一個(gè)右值。
需要注意的一點(diǎn)是:
右值引用變量本身也是一個(gè)左值!

對(duì)于這句話(huà)的理解是:
一個(gè)右值(臨時(shí)量),是沒(méi)有名字的;
一個(gè)引用,就是相當(dāng)于給變量起了別名;
右值和引用一結(jié)合,就有了名字、有了內(nèi)存;
那么也就變?yōu)榱俗笾怠?/p>

這一點(diǎn)特別重要:在后面move和forward應(yīng)用的時(shí)候需要特別注意!

接下來(lái)我們就可以在原來(lái)CMyString類(lèi)型里添加帶有右值引用參數(shù)的成員方法(主要是拷貝構(gòu)造和賦值重載):

// 帶右值引用參數(shù)的拷貝構(gòu)造 CMyString(CMyString &&str) // str引用的就是一個(gè)臨時(shí)對(duì)象 {cout << "CMyString(CMyString&&)" << endl;_pstr= str._pstr;str._pstr= nullptr; }// 帶右值引用參數(shù)的賦值重載函數(shù) CMyString& operator=(CMyString &&str) // 臨時(shí)對(duì)象 {cout << "operator=(CMyString&&)" << endl;if (this == &str)return *this;delete[]_pstr;_pstr= str._pstr;str._pstr = nullptr;return *this; }

結(jié)果對(duì)比

修改代碼之后再次運(yùn)行,結(jié)果如下:

我們可以看到,這一次,匹配的都是右值引用參數(shù)的成員方法了!

解決遺留問(wèn)題

了解了右值引用之后,我們發(fā)現(xiàn),右值引用對(duì)于解決涉及臨時(shí)對(duì)象大量?jī)?nèi)存開(kāi)辟及數(shù)據(jù)拷貝的問(wèn)題有著很好的應(yīng)用。
所以,我們之前遺留的問(wèn)題:
CMyString的+運(yùn)算符重載函數(shù)就可以解決了:

CMyString operator+(const CMyString &lhs,const CMyString &rhs) {CMyString tmpStr;tmpStr.mptr = new char[strlen(lhs.mptr) + strlen(rhs.mptr) + 1];strcpy(tmpStr.mptr, lhs.mptr);strcat(tmpStr.mptr, rhs.mptr);return tmpStr; }

注意:
在類(lèi)外定義+運(yùn)算符重載函數(shù)的時(shí)候,需要在類(lèi)里面定義一個(gè)友元函數(shù):

class CMyString { private:friend CMyString operator+(const CMyString &lhs,const CMyString &rhs); };

這樣的話(huà),在涉及臨時(shí)對(duì)象的拷貝構(gòu)造和賦值重載,都將匹配到帶有右值引用參數(shù)的成員方法。
這樣就可以避免大量開(kāi)辟內(nèi)存和數(shù)據(jù)拷貝了!
代碼的效率得到了極大的提高!

總結(jié)

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

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