亚洲欧美第一页_禁久久精品乱码_粉嫩av一区二区三区免费野_久草精品视频

蟲蟲首頁| 資源下載| 資源專輯| 精品軟件
登錄| 注冊

您現(xiàn)在的位置是:首頁 > 技術(shù)閱讀 >  右值引用的意義!

右值引用的意義!

時(shí)間:2024-02-12


文章來源:

知乎|作者:Tinro

右值引用是C++11中最重要的新特性之一,它解決了C++中大量的歷史遺留問題,使C++標(biāo)準(zhǔn)庫的實(shí)現(xiàn)在多種場景下消除了不必要的額外開銷(如std::vector, std::string),也使得另外一些標(biāo)準(zhǔn)庫(如std::unique_ptr, std::function)成為可能。即使你并不直接使用右值引用,也可以通過標(biāo)準(zhǔn)庫,間接從這一新特性中受益。為了更好的理解標(biāo)準(zhǔn)庫結(jié)合右值引用帶來的優(yōu)化,我們有必要了解一下右值引用的重大意義。


右值引用的意義通常解釋為兩大作用:移動(dòng)語義和完美轉(zhuǎn)發(fā)。本文主要討論移動(dòng)語義。


------移動(dòng)語義------


移動(dòng)語義,簡單來說解決的是各種情形下對(duì)象的資源所有權(quán)轉(zhuǎn)移的問題。而在C++11之前,移動(dòng)語義的缺失是C++飽受詬病的問題之一。


舉個(gè)栗子。


問題一:如何將大象放入冰箱?
答案是眾所周知的。首先你需要有一臺(tái)特殊的冰箱,這臺(tái)冰箱是為了裝下大象而制造的。你打開冰箱門,將大象放入冰箱,然后關(guān)上冰箱門。


問題二:如何將大象從一臺(tái)冰箱轉(zhuǎn)移到另一臺(tái)冰箱?
普通解答:打開冰箱門,取出大象,關(guān)上冰箱門,打開另一臺(tái)冰箱門,放進(jìn)大象,關(guān)上冰箱門。


2B解答:在第二個(gè)冰箱中啟動(dòng)量子復(fù)制系統(tǒng),克隆一只完全相同的大象,然后啟動(dòng)高能激光將第一個(gè)冰箱內(nèi)的大象氣化消失。


等等,這個(gè)2B解答聽起來很耳熟,這不就是C++中要移動(dòng)一個(gè)對(duì)象時(shí)所做的事情嗎?


“移動(dòng)”,這是一個(gè)三歲小孩都明白的概念。將大象(資源)從一臺(tái)冰箱(對(duì)象)移動(dòng)到另一臺(tái)冰箱,這個(gè)行為是如此自然,沒有任何人會(huì)采用先復(fù)制大象,再銷毀大象這樣匪夷所思的方法。C++通過拷貝構(gòu)造函數(shù)和拷貝賦值操作符為類設(shè)計(jì)了拷貝/復(fù)制的概念,但為了實(shí)現(xiàn)對(duì)資源的移動(dòng)操作,調(diào)用者必須使用先復(fù)制、再析構(gòu)的方式。否則,就需要自己實(shí)現(xiàn)移動(dòng)資源的接口。


為了實(shí)現(xiàn)移動(dòng)語義,首先需要解決的問題是,如何標(biāo)識(shí)對(duì)象的資源是可以被移動(dòng)的呢?這種機(jī)制必須以一種最低開銷的方式實(shí)現(xiàn),并且對(duì)所有的類都有效。C++的設(shè)計(jì)者們注意到,大多數(shù)情況下,右值所包含的對(duì)象都是可以安全的被移動(dòng)的。

右值(相對(duì)應(yīng)的還有左值)是從C語言設(shè)計(jì)時(shí)就有的概念,但因?yàn)槠淙绱嘶A(chǔ),也是一個(gè)最常被忽略的概念。不嚴(yán)格的來說,左值對(duì)應(yīng)變量的存儲(chǔ)位置,而右值對(duì)應(yīng)變量的值本身。C++中右值可以被賦值給左值或者綁定到引用。類的右值是一個(gè)臨時(shí)對(duì)象,如果沒有被綁定到引用,在表達(dá)式結(jié)束時(shí)就會(huì)被廢棄。于是我們可以在右值被廢棄之前,移走它的資源進(jìn)行廢物利用,從而避免無意義的復(fù)制。被移走資源的右值在廢棄時(shí)已經(jīng)成為空殼,析構(gòu)的開銷也會(huì)降低。


右值中的數(shù)據(jù)可以被安全移走這一特性使得右值被用來表達(dá)移動(dòng)語義。以同類型的右值構(gòu)造對(duì)象時(shí),需要以引用形式傳入?yún)?shù)。右值引用顧名思義專門用來引用右值,左值引用和右值引用可以被分別重載,這樣確保左值和右值分別調(diào)用到拷貝和移動(dòng)的兩種語義實(shí)現(xiàn)。對(duì)于左值,如果我們明確放棄對(duì)其資源的所有權(quán),則可以通過std::move()來將其轉(zhuǎn)為右值引用。std::move()實(shí)際上是static_cast<T&&>()的簡單封裝。


右值引用至少可以解決以下場景中的移動(dòng)語義缺失問題:


  • 按值傳入?yún)?shù)


按值傳參是最符合人類思維的方式。基本的思路是,如果傳入?yún)?shù)是為了將資源交給函數(shù)接受者,就應(yīng)該按值傳參。同時(shí),按值傳參可以兼容任何的cv-qualified左值、右值,是兼容性最好的方式。

class People {public:  People(string name) // 按值傳入字符串,可接收左值、右值。接收左值時(shí)為復(fù)制,接收右值時(shí)為移動(dòng)  : name_(move(name)) // 顯式移動(dòng)構(gòu)造,將傳入的字符串移入成員變量  {  }  string name_;};
People a("Alice"); // 移動(dòng)構(gòu)造name
string bn = "Bob";People b(bn); // 拷貝構(gòu)造name

構(gòu)造a時(shí),調(diào)用了一次字符串的構(gòu)造函數(shù)和一次字符串的移動(dòng)構(gòu)造函數(shù)。如果使用const string& name接收參數(shù),那么會(huì)有一次構(gòu)造函數(shù)和一次拷貝構(gòu)造,以及一次non-trivial的析構(gòu)。盡管看起來很蛋疼,盡管編譯器還有優(yōu)化,但從語義來說按值傳入?yún)?shù)是最優(yōu)的方式。


如果你要在構(gòu)造函數(shù)中接收std::shared_ptr<X>并且存入類的成員(這是非常常見的),那么按值傳入更是不二選擇。拷貝std::shared_ptr<X>需要線程同步,相比之下移動(dòng)std::shared_ptr是非常輕松愉快的。


  • 按值返回


和接收輸入?yún)?shù)一樣,返回值按值返回也是最符合人類思維的方式。曾經(jīng)有無數(shù)函數(shù)為了返回容器而不得不寫成這樣

void str_split(const string& s, vector<string>* vec); // 一個(gè)按值語義定義的字符串拆分函數(shù)。這里不考慮分隔符,假定分隔符是固定的。


這樣要求vec在外部被事先構(gòu)造,此時(shí)尚無從得知vec的大小。即使函數(shù)內(nèi)部有辦法預(yù)測vec的大小,因?yàn)楹瘮?shù)并不負(fù)責(zé)構(gòu)造vec,很可能仍需要resize。


對(duì)這樣的函數(shù)嵌套調(diào)用更是痛苦的事情,誰用誰知道啊。


有了移動(dòng)語義,就可以寫成這樣

vector<string> str_split(const string& s) {  vector<string> v;  // ...  return v; // v是左值,但優(yōu)先移動(dòng),不支持移動(dòng)時(shí)仍可復(fù)制。}


如果函數(shù)按值返回,return語句又直接返回了一個(gè)棧上的左值對(duì)象(輸入?yún)?shù)除外)時(shí),標(biāo)準(zhǔn)要求優(yōu)先調(diào)用移動(dòng)構(gòu)造函數(shù),如果不符再調(diào)用拷貝構(gòu)造函數(shù)。盡管v是左值,仍然會(huì)優(yōu)先采用移動(dòng)語義,返回vector<string>從此變得云淡風(fēng)輕。此外,無論移動(dòng)或是拷貝,可能的情況下仍然適用編譯器優(yōu)化,但語義不受影響。

對(duì)于std::unique_ptr來說,這簡直就是福音。

unique_ptr<SomeObj> create_obj(/*...*/) {  unique_ptr<SomeObj> ptr(new SomeObj(/*...*/));  ptr->foo(); // 一些可能的初始化  return ptr;}


當(dāng)然還有更簡單的形式

unique_ptr<SomeObj> create_obj(/*...*/) {  return unique_ptr<SomeObj>(new SomeObj(/*...*/));}

在工廠類中,這樣的語義是非常常見的。返回unique_ptr能夠明確對(duì)所構(gòu)造對(duì)象的所有權(quán)轉(zhuǎn)移,特別的,這樣的工廠類返回值可以被忽略而不會(huì)造成內(nèi)存泄露。上面兩種形式分別返回棧上的左值和右值,但都適用移動(dòng)語義(unique_ptr不支持拷貝)。


  • 接收右值表達(dá)式


沒有移動(dòng)語義時(shí),以表達(dá)式的值(例為函數(shù)調(diào)用)初始化對(duì)象或者給對(duì)象賦值是這樣的:

vector<string> str_split(const string& s);
vector<string> v = str_split("1,2,3"); // 返回的vector用以拷貝構(gòu)造對(duì)象v。為v申請(qǐng)堆內(nèi)存,復(fù)制數(shù)據(jù),然后析構(gòu)臨時(shí)對(duì)象(釋放堆內(nèi)存)。vector<string> v2;v2 = str_split("1,2,3"); // 返回的vector被復(fù)制給對(duì)象v(拷貝賦值操作符)。需要先清理v2中原有數(shù)據(jù),將臨時(shí)對(duì)象中的數(shù)據(jù)復(fù)制給v2,然后析構(gòu)臨時(shí)對(duì)象。


注:v的拷貝構(gòu)造調(diào)用有可能被優(yōu)化掉,盡管如此在語義上仍然是有一次拷貝操作。


同樣的代碼,在支持移動(dòng)語義的世界里就變得更美好了。

vector<string> str_split(const string& s);
vector<string> v = str_split("1,2,3"); // 返回的vector用以移動(dòng)構(gòu)造對(duì)象v。v直接取走臨時(shí)對(duì)象的堆上內(nèi)存,無需新申請(qǐng)。之后臨時(shí)對(duì)象成為空殼,不再擁有任何資源,析構(gòu)時(shí)也無需釋放堆內(nèi)存。vector<string> v2;v2 = str_split("1,2,3"); // 返回的vector被移動(dòng)給對(duì)象v(移動(dòng)賦值操作符)。先釋放v2原有數(shù)據(jù),然后直接從返回值中取走數(shù)據(jù),然后返回值被析構(gòu)。

注:v的移動(dòng)構(gòu)造調(diào)用有可能被優(yōu)化掉,盡管如此在語義上仍然是有一次移動(dòng)操作。


不用多說也知道上面的形式是多么常用和自然。而且這里完全沒有任何對(duì)右值引用的顯式使用,性能提升卻默默的實(shí)現(xiàn)了。


  • 對(duì)象存入容器


這個(gè)問題和前面的構(gòu)造函數(shù)傳參是類似的。不同的是這里是按兩種引用分別傳參。參見std::vector的push_back函數(shù)。


void push_back( const T& value ); // (1)void push_back( T&& value ); // (2)

不用多說自然是左值調(diào)用1右值調(diào)用2。如果你要往容器內(nèi)放入超大對(duì)象,那么版本2自然是不2選擇。

vector<vector<string>> vv;
vector<string> v = {"123", "456"};v.push_back("789"); // 臨時(shí)構(gòu)造的string類型右值被移動(dòng)進(jìn)容器vvv.push_back(move(v)); // 顯式將v移動(dòng)進(jìn)vv

困擾多年的難言之隱是不是一洗了之了?


  • std::vector的增長


又一個(gè)隱蔽的優(yōu)化。當(dāng)vector的存儲(chǔ)容量需要增長時(shí),通常會(huì)重新申請(qǐng)一塊內(nèi)存,并把原來的內(nèi)容一個(gè)個(gè)復(fù)制過去并刪除。對(duì),復(fù)制并刪除,改用移動(dòng)就夠了。


對(duì)于像vector<string>這樣的容器,如果頻繁插入造成存儲(chǔ)容量不可避免的增長時(shí),移動(dòng)語義可以帶來悄無聲息而且美好的優(yōu)化。


  • std::unique_ptr放入容器


曾經(jīng),由于vector增長時(shí)會(huì)復(fù)制對(duì)象,像std::unique_ptr這樣不可復(fù)制的對(duì)象是無法放入容器的。但實(shí)際上vector并不復(fù)制對(duì)象,而只是“移動(dòng)”對(duì)象。所以隨著移動(dòng)語義的引入,std::unique_ptr放入std::vector成為理所當(dāng)然的事情。


容器中存儲(chǔ)std::unique_ptr有太多好處。想必每個(gè)人都寫過這樣的代碼:

MyObj::MyObj() {  for (...) {    vec.push_back(new T());  }  // ...}
MyObj::~MyObj() { for (vector<T*>::iterator iter = vec.begin(); iter != vec.end(); ++iter) { if (*iter) delete *iter; } // ...}

繁瑣暫且不說,異常安全也是大問題。使用vector<unique_ptr<T>>,完全無需顯式析構(gòu),unqiue_ptr自會(huì)打理一切。完全不用寫析構(gòu)函數(shù)的感覺,你造嗎?


unique_ptr是非常輕量的封裝,存儲(chǔ)空間等價(jià)于裸指針,但安全性強(qiáng)了一個(gè)世紀(jì)。實(shí)際中需要共享所有權(quán)的對(duì)象(指針)是比較少的,但需要轉(zhuǎn)移所有權(quán)是非常常見的情況。auto_ptr的失敗就在于其轉(zhuǎn)移所有權(quán)的繁瑣操作。unique_ptr配合移動(dòng)語義即可輕松解決所有權(quán)傳遞的問題。


注:如果真的需要共享所有權(quán),那么基于引用計(jì)數(shù)的shared_ptr是一個(gè)好的選擇。shared_ptr同樣可以移動(dòng)。由于不需要線程同步,移動(dòng)shared_ptr比復(fù)制更輕量。


  • std::thread的傳遞


thread也是一種典型的不可復(fù)制的資源,但可以通過移動(dòng)來傳遞所有權(quán)。同樣std::future std::promise std::packaged_task等等這一票多線程類都是不可復(fù)制的,也都可以用移動(dòng)的方式傳遞。


------完美轉(zhuǎn)發(fā)------


除了移動(dòng)語義,右值引用還解決了C++03中引用語法無法轉(zhuǎn)發(fā)右值的問題,實(shí)現(xiàn)了完美轉(zhuǎn)發(fā),才使得std::function能有一個(gè)優(yōu)雅的實(shí)現(xiàn)。這部分不再展開了。


------總結(jié)------


移動(dòng)語義絕不是語法糖,而是帶來了C++的深刻革新。移動(dòng)語義不僅僅是針對(duì)庫作者的,任何一個(gè)程序員都有必要去了解它。盡管你可能不會(huì)去主動(dòng)為自己的類實(shí)現(xiàn)移動(dòng)語義,但卻時(shí)時(shí)刻刻都在享受移動(dòng)語義帶來的受益。因此這絕不意味著這是一個(gè)可有可無的東西。


C++學(xué)習(xí)資料免費(fèi)獲取方法:關(guān)注程序喵大人,后臺(tái)回復(fù)“程序喵”即可免費(fèi)獲取40萬字C++進(jìn)階獨(dú)家學(xué)習(xí)資料。





往期推薦


1、少寫點(diǎn)
if-else吧,它的效率有多低你知道嗎?
2、年度原創(chuàng)好文匯總
3、全網(wǎng)首發(fā)!!C++20新特性全在這一張圖里了
4
他來了,他來了,C+
+17新特性精華都在這了
5、一文讓你搞懂設(shè)計(jì)模式
6、C++11新特性,所有知識(shí)點(diǎn)都在這了!


亚洲欧美第一页_禁久久精品乱码_粉嫩av一区二区三区免费野_久草精品视频
免费观看一区| 亚洲欧美激情在线视频| 久久国产精品黑丝| 国内精品视频在线观看| 久久嫩草精品久久久精品一| 亚洲丰满在线| 欧美精品v国产精品v日韩精品| 一区二区三区精品在线| 国产乱子伦一区二区三区国色天香| 欧美在线视频a| 亚洲国产另类久久久精品极度| 欧美日韩免费精品| 午夜精品免费视频| 亚洲大胆美女视频| 欧美日韩一本到| 久久精品动漫| 亚洲乱亚洲高清| 国产三区精品| 欧美日韩国产大片| 久久久久久久综合日本| 在线性视频日韩欧美| 国产在线拍偷自揄拍精品| 欧美国产激情| 久久精品国产欧美亚洲人人爽| 亚洲精品偷拍| 国产在线欧美日韩| 欧美视频中文一区二区三区在线观看 | 国产精品高清网站| 老司机凹凸av亚洲导航| 亚洲一区二区三区中文字幕在线| 国产亚洲欧美激情| 国产精品ⅴa在线观看h| 欧美va日韩va| 久久久免费观看视频| 亚洲网友自拍| 亚洲青色在线| 国内精品久久久久久影视8| 欧美午夜www高清视频| 蘑菇福利视频一区播放| 欧美与黑人午夜性猛交久久久| 亚洲精品在线观| 一区二区在线观看视频| 国产区亚洲区欧美区| 国产精品盗摄久久久| 欧美精品91| 久久亚洲国产精品日日av夜夜| 午夜精品视频在线观看| 正在播放欧美视频| 亚洲精品一线二线三线无人区| 国内精品久久久| 国产女优一区| 国产精品久久久久影院色老大| 欧美福利视频在线观看| 久久久噜噜噜久久中文字免| 亚洲欧美视频一区| 在线视频精品一区| 亚洲精品中文字幕女同| 在线看国产一区| 永久久久久久| 精品电影在线观看| 国产视频精品va久久久久久| 国产女主播一区| 国产精品久久午夜夜伦鲁鲁| 欧美三级午夜理伦三级中视频| 欧美成ee人免费视频| 久久综合给合| 久久亚洲精品视频| 久久香蕉精品| 久久久精品国产99久久精品芒果| 亚洲欧美日韩精品久久| 亚洲精品视频一区| 欧美日在线观看| 欧美午夜大胆人体| 欧美系列精品| 国产精品自在在线| 国产欧美91| 国产欧美成人| 国产日韩亚洲| 国产一区二区三区观看| 国产亚洲精品一区二区| 国产一区二区三区的电影| 国内成+人亚洲| 在线观看亚洲视频| 亚洲日本在线视频观看| 亚洲区欧美区| 日韩亚洲一区在线播放| 正在播放亚洲| 性欧美1819性猛交| 久久―日本道色综合久久| 欧美在线国产| 久久久久九九九九| 免费在线观看一区二区| 久久综合影音| 欧美日产在线观看| 欧美精品色一区二区三区| 欧美日韩亚洲视频| 国产精品狼人久久影院观看方式| 国产精品一二三四| 国产字幕视频一区二区| 亚洲欧美韩国| 欧美一区二粉嫩精品国产一线天| 久久精品国产69国产精品亚洲 | 国产精品丝袜白浆摸在线| 欧美日韩中文| 国产午夜精品久久| 亚洲国产精品女人久久久| 日韩亚洲视频| 欧美夜福利tv在线| 麻豆精品视频在线| 国产精品美女一区二区| 一区二区三区在线不卡| 99pao成人国产永久免费视频| 亚洲一区二区三区在线| 久久久久久久久久久一区| 欧美激情一区二区三区全黄| 国产精品高潮视频| 激情文学综合丁香| 一本色道久久综合亚洲精品婷婷| 午夜亚洲影视| 欧美福利专区| 国产精品―色哟哟| 亚洲第一综合天堂另类专| 一区二区三区欧美视频| 久久久视频精品| 欧美亚州韩日在线看免费版国语版| 国产日韩欧美一二三区| 亚洲免费观看在线视频| 夜夜精品视频一区二区| 久久精品亚洲一区二区| 欧美三区美女| **性色生活片久久毛片| 亚洲欧美视频一区二区三区| 欧美高清免费| 国产一区二区三区在线观看精品| 99国产精品自拍| 久久综合九色99| 国产精品草草| 亚洲人成在线免费观看| 久久9热精品视频| 欧美日韩午夜激情| 亚洲国产高潮在线观看| 久久精品官网| 欧美日韩一区二区三区免费看| 国产在线观看91精品一区| 亚洲激情一区| 久久久国产午夜精品| 欧美午夜三级| 亚洲精品乱码久久久久久按摩观| 久久av一区二区| 欧美亚洲不卡| 999在线观看精品免费不卡网站| 久久日韩精品| 国产亚洲人成a一在线v站| 亚洲网站视频福利| 性欧美在线看片a免费观看| 欧美日韩国产精品一区二区亚洲| 欲香欲色天天天综合和网| 国产美女一区二区| 亚洲国产婷婷| 久久欧美肥婆一二区| 国产区亚洲区欧美区| 亚洲一区二区三区成人在线视频精品| 欧美aⅴ99久久黑人专区| 合欧美一区二区三区| 欧美一区二区三区免费视频| 国产精品久久久久久影院8一贰佰| 亚洲精品乱码久久久久久按摩观 | 午夜视频久久久| 国产精品福利在线观看网址| 亚洲美女在线看| 欧美国产一区二区| 韩国成人福利片在线播放| 亚洲在线一区二区| 国产精品久久久久久久久久尿| 一区二区激情| 欧美精品一区三区| 亚洲精品在线视频观看| 欧美精品一线| 亚洲精品影视在线观看| 欧美经典一区二区三区| 亚洲精选一区| 欧美日韩午夜| 亚洲综合视频一区| 欧美一区二区三区的| 国产亚洲视频在线观看| 久久久精品国产一区二区三区| 狠狠狠色丁香婷婷综合激情| 久久天天躁狠狠躁夜夜爽蜜月| 国产一区二区黄色| 久久久91精品国产一区二区三区 | 国产精品国产三级国产普通话三级 | 欧美日韩国产不卡| 一区二区久久久久| 国产精品久久久久影院亚瑟| 亚洲欧美日韩直播| 国产无一区二区| 欧美成人中文字幕在线| 亚洲乱码久久| 国产精品网曝门| 久久视频在线视频|