當(dāng)前位置:首頁 > 公眾號(hào)精選 > 后端技術(shù)指南針
[導(dǎo)讀]眾所周知C++11新增了右值引用,談?dòng)抑狄梦覀円部梢詳U(kuò)展一些相關(guān)概念: 左值 右值 純右值 將亡值 左值引用 右值引用 移動(dòng)語義 完美轉(zhuǎn)發(fā) 返回值優(yōu)化 程序喵下面會(huì)一一介紹: 左值、右值 概念1: 左值:可以放到等號(hào)左邊的東西叫左值。 右值:不可以放到等號(hào)左

眾所周知C++11新增了右值引用,談?dòng)抑狄梦覀円部梢詳U(kuò)展一些相關(guān)概念:

  • 左值

  • 右值

  • 純右值

  • 將亡值

  • 左值引用

  • 右值引用

  • 移動(dòng)語義

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

  • 返回值優(yōu)化

程序喵下面會(huì)一一介紹:

左值、右值

概念1

左值:可以放到等號(hào)左邊的東西叫左值。

右值:不可以放到等號(hào)左邊的東西就叫右值。

概念2

左值:可以取地址并且有名字的東西就是左值。

右值:不能取地址的沒有名字的東西就是右值。

舉例

int a = b + c; 

a是左值,有變量名,可以取地址,也可以放到等號(hào)左邊, 表達(dá)式b+c的返回值是右值,沒有名字且不能取地址,&(b+c)不能通過編譯,而且也不能放到等號(hào)左邊。

int a = 4; // a是左值,4作為普通字面量是右值

左值一般有:

  • 函數(shù)名和變量名

  • 返回左值引用的函數(shù)調(diào)用

  • 前置自增自減表達(dá)式++i、--i

  • 由賦值表達(dá)式或賦值運(yùn)算符連接的表達(dá)式(a=b, a += b等)

  • 解引用表達(dá)式*p

  • 字符串字面值"abcd"

純右值、將亡值

純右值和將亡值都屬于右值。

純右值

運(yùn)算表達(dá)式產(chǎn)生的臨時(shí)變量、不和對(duì)象關(guān)聯(lián)的原始字面量、非引用返回的臨時(shí)變量、lambda表達(dá)式等都是純右值。

舉例:

  • 除字符串字面值外的字面值

  • 返回非引用類型的函數(shù)調(diào)用

  • 后置自增自減表達(dá)式i++、i--

  • 算術(shù)表達(dá)式(a+b, a*b, a&&b, a==b等)

  • 取地址表達(dá)式等(&a)

將亡值

將亡值是指C++11新增的和右值引用相關(guān)的表達(dá)式,通常指將要被移動(dòng)的對(duì)象、T&&函數(shù)的返回值、std::move函數(shù)的返回值、轉(zhuǎn)換為T&&類型轉(zhuǎn)換函數(shù)的返回值,將亡值可以理解為即將要銷毀的值,通過“盜取”其它變量內(nèi)存空間方式獲取的值,在確保其它變量不再被使用或者即將被銷毀時(shí),可以避免內(nèi)存空間的釋放和分配,延長變量值的生命周期,常用來完成移動(dòng)構(gòu)造或者移動(dòng)賦值的特殊任務(wù)。

舉例:

class A { xxx;};A a;auto c = std::move(a); // c是將亡值auto d = static_cast<A&&>(a); // d是將亡值
左值引用、右值引用

根據(jù)名字大概就可以猜到意思,左值引用就是對(duì)左值進(jìn)行引用的類型,右值引用就是對(duì)右值進(jìn)行引用的類型,他們都是引用,都是對(duì)象的一個(gè)別名,并不擁有所綁定對(duì)象的堆存,所以都必須立即初始化。

type &name = exp; // 左值引用type &&name = exp; // 右值引用
左值引用

看代碼:

int a = 5;int &b = a; // b是左值引用b = 4;int &c = 10; // error,10無法取地址,無法進(jìn)行引用const int &d = 10; // ok,因?yàn)槭浅R?,引用常量?shù)字,這個(gè)常量數(shù)字會(huì)存儲(chǔ)在內(nèi)存中,可以取地址

可以得出結(jié)論: 對(duì)于左值引用,等號(hào)右邊的值必須可以取地址,如果不能取地址,則會(huì)編譯失敗,或者可以使用const引用形式,但這樣就只能通過引用來讀取輸出,不能修改數(shù)組,因?yàn)槭浅A恳谩?/span>

右值引用

如果使用右值引用,那表達(dá)式等號(hào)右邊的值需要時(shí)右值,可以使用std::move函數(shù)強(qiáng)制把左值轉(zhuǎn)換為右值。

int a = 4;int &&b = a; // error, a是左值int &&c = std::move(a); // ok

移動(dòng)語義

談移動(dòng)語義前,我們首先需要了解深拷貝與淺拷貝的概念

深拷貝、淺拷貝

直接拿代碼舉例:

class A {public: A(int size) : size_(size) { data_ = new int[size]; } A(){} A(const A& a) { size_ = a.size_; data_ = a.data_; cout << "copy " << endl; } ~A() { delete[] data_; } int *data_; int size_;};int main() { A a(10); A b = a; cout << "b " << b.data_ << endl; cout << "a " << a.data_ << endl; return 0;}

上面代碼中,兩個(gè)輸出的是相同的地址,a和b的data_指針指向了同一塊內(nèi)存,這就是淺拷貝,只是數(shù)據(jù)的簡單賦值,那再析構(gòu)時(shí)data_內(nèi)存會(huì)被釋放兩次,導(dǎo)致程序出問題,這里正常會(huì)出現(xiàn)double free導(dǎo)致程序崩潰的,但是不知道為什么我自己測試程序卻沒有崩潰,能力有限,沒搞明白,無論怎樣,這樣的程序肯定是有隱患的,如何消除這種隱患呢,可以使用如下深拷貝:

class A {public: A(int size) : size_(size) { data_ = new int[size]; } A(){} A(const A& a) { size_ = a.size_; data_ = new int[size_]; cout << "copy " << endl; } ~A() { delete[] data_; } int *data_; int size_;};int main() { A a(10); A b = a; cout << "b " << b.data_ << endl; cout << "a " << a.data_ << endl; return 0;}
深拷貝就是再拷貝對(duì)象時(shí),如果被拷貝對(duì)象內(nèi)部還有指針引用指向其它資源,自己需要重新開辟一塊新內(nèi)存存儲(chǔ)資源,而不是簡單的賦值。

聊完了深拷貝淺拷貝,可以聊聊移動(dòng)語義啦:

移動(dòng)語義,在程序喵看來可以理解為轉(zhuǎn)移所有權(quán),之前的拷貝是對(duì)于別人的資源,自己重新分配一塊內(nèi)存存儲(chǔ)復(fù)制過來的資源,而對(duì)于移動(dòng)語義,類似于轉(zhuǎn)讓或者資源竊取的意思,對(duì)于那塊資源,轉(zhuǎn)為自己所擁有,別人不再擁有也不會(huì)再使用,通過C++11新增的移動(dòng)語義可以省去很多拷貝負(fù)擔(dān),怎么利用移動(dòng)語義呢,是通過移動(dòng)構(gòu)造函數(shù)。

class A {public: A(int size) : size_(size) { data_ = new int[size]; } A(){} A(const A& a) { size_ = a.size_; data_ = new int[size_]; cout << "copy " << endl; } A(A&& a) { this->data_ = a.data_; a.data_ = nullptr; cout << "move " << endl; } ~A() { if (data_ != nullptr) { delete[] data_; } } int *data_; int size_;};int main() { A a(10); A b = a; A c = std::move(a); // 調(diào)用移動(dòng)構(gòu)造函數(shù) return 0;}
如果不使用std::move(),會(huì)有很大的拷貝代價(jià),使用移動(dòng)語義可以避免很多無用的拷貝,提供程序性能,C++所有的STL都實(shí)現(xiàn)了移動(dòng)語義,方便我們使用。例如:
std::vector<string> vecs;...std::vector<string> vecm = std::move(vecs); // 免去很多拷貝
注意: 移動(dòng)語義僅針對(duì)于那些實(shí)現(xiàn)了移動(dòng)構(gòu)造函數(shù)的類的對(duì)象,對(duì)于那種基本類型int、float等沒有任何優(yōu)化作用,還是會(huì)拷貝,因?yàn)樗鼈儗?shí)現(xiàn)沒有對(duì)應(yīng)的移動(dòng)構(gòu)造函數(shù)。

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

完美轉(zhuǎn)發(fā)指可以寫一個(gè)接受任意實(shí)參的函數(shù)模板,并轉(zhuǎn)發(fā)到其它函數(shù),目標(biāo)函數(shù)會(huì)收到與轉(zhuǎn)發(fā)函數(shù)完全相同的實(shí)參,轉(zhuǎn)發(fā)函數(shù)實(shí)參是左值那目標(biāo)函數(shù)實(shí)參也是左值,轉(zhuǎn)發(fā)函數(shù)實(shí)參是右值那目標(biāo)函數(shù)實(shí)參也是右值。那如何實(shí)現(xiàn)完美轉(zhuǎn)發(fā)呢,答案是使用std::forward()。

void PrintV(int &t) { cout << "lvalue" << endl;}
void PrintV(int &&t) { cout << "rvalue" << endl;}
template<typename T>void Test(T &&t) { PrintV(t); PrintV(std::forward<T>(t));
PrintV(std::move(t));}
int main() { Test(1); // lvalue rvalue rvalue int a = 1; Test(a); // lvalue lvalue rvalue Test(std::forward<int>(a)); // lvalue rvalue rvalue Test(std::forward<int&>(a)); // lvalue lvalue rvalue Test(std::forward<int&&>(a)); // lvalue rvalue rvalue return 0;}

分析

  • Test(1):1是右值,模板中T &&t這種為萬能引用,右值1傳到Test函數(shù)中變成了右值引用,但是調(diào)用PrintV()時(shí)候,t變成了左值,因?yàn)樗兂闪艘粋€(gè)擁有名字的變量,所以打印lvalue,而PrintV(std::forward<T>(t))時(shí)候,會(huì)進(jìn)行完美轉(zhuǎn)發(fā),按照原來的類型轉(zhuǎn)發(fā),所以打印rvalue,PrintV(std::move(t))毫無疑問會(huì)打印rvalue。

  • Test(a):a是左值,模板中T &&這種為萬能引用,左值a傳到Test函數(shù)中變成了左值引用,所以有代碼中打印。

  • Test(std::forward<T>(a)):轉(zhuǎn)發(fā)為左值還是右值,依賴于T,T是左值那就轉(zhuǎn)發(fā)為左值,T是右值那就轉(zhuǎn)發(fā)為右值。

返回值優(yōu)化

返回值優(yōu)化(RVO)是一種C++編譯優(yōu)化技術(shù),當(dāng)函數(shù)需要返回一個(gè)對(duì)象實(shí)例時(shí)候,就會(huì)創(chuàng)建一個(gè)臨時(shí)對(duì)象并通過復(fù)制構(gòu)造函數(shù)將目標(biāo)對(duì)象復(fù)制到臨時(shí)對(duì)象,這里有復(fù)制構(gòu)造函數(shù)和析構(gòu)函數(shù)會(huì)被多余的調(diào)用到,有代價(jià),而通過返回值優(yōu)化,C++標(biāo)準(zhǔn)允許省略調(diào)用這些復(fù)制構(gòu)造函數(shù)。

那什么時(shí)候編譯器會(huì)進(jìn)行返回值優(yōu)化呢?

  • return的值類型與函數(shù)的返回值類型相同

  • return的是一個(gè)局部對(duì)象

看幾個(gè)例子:

示例1:

std::vector<int> return_vector(void) { std::vector<int> tmp {1,2,3,4,5}; return tmp;}std::vector<int> &&rval_ref = return_vector();
不會(huì)觸發(fā)RVO,拷貝構(gòu)造了一個(gè)臨時(shí)的對(duì)象,臨時(shí)對(duì)象的生命周期和rval_ref綁定,等價(jià)于下面這段代碼:
const std::vector<int>& rval_ref = return_vector();

示例2:

std::vector<int>&& return_vector(void) { std::vector<int> tmp {1,2,3,4,5}; return std::move(tmp);}
std::vector<int> &&rval_ref = return_vector();
這段代碼會(huì)造成運(yùn)行時(shí)錯(cuò)誤,因?yàn)閞val_ref引用了被析構(gòu)的tmp。講道理來說這段代碼是錯(cuò)的,但我自己運(yùn)行過程中卻成功了,我沒有那么幸運(yùn),這里不糾結(jié),繼續(xù)向下看什么時(shí)候會(huì)觸發(fā)RVO。

示例3:

std::vector<int> return_vector(void) { std::vector<int> tmp {1,2,3,4,5}; return std::move(tmp);}
std::vector<int> &&rval_ref = return_vector();

和示例1類似,std::move一個(gè)臨時(shí)對(duì)象是沒有必要的,也會(huì)忽略掉返回值優(yōu)化。

最好的代碼:

std::vector<int> return_vector(void) { std::vector<int> tmp {1,2,3,4,5}; return tmp;}
std::vector<int> rval_ref = return_vector();

這段代碼會(huì)觸發(fā)RVO,不拷貝也不移動(dòng),不生成臨時(shí)對(duì)象。

參考資料

《Effective Modern C++》
《深入應(yīng)用C++11:代碼優(yōu)化與工程級(jí)應(yīng)用》
https://blog.csdn.net/u0105
https://www.jianshu.com/p/4538483a1d8a
https://www.cnblogs.com/xkfz007/articles/2506022.html
https://zhuanlan.zhihu.com/p/97128024
https://zh.cppreference.com/w/cpp/utility/forward
https://www.zhihu.com/question/43513150
https://stackoverflow.com/questions/4986673/c11-rvalues-and-move-semantics-confusion-return-statement?lq=1





一文讓你搞懂設(shè)計(jì)模式

RAII妙用之ScopeExit

深入淺出虛擬內(nèi)存

深入淺出虛擬內(nèi)存(二)繪制虛擬內(nèi)存排布圖

深入淺出虛擬內(nèi)存(三)堆內(nèi)存分配及malloc實(shí)現(xiàn)原理

RAII妙用之計(jì)算函數(shù)耗時(shí)

一文吃透C++11中auto和decltype知識(shí)點(diǎn)





如果有任何問題或想法,可以 點(diǎn)此留言 ,我會(huì)盡快回復(fù)噠!歡迎小伙伴們踴躍留言,希望這里是大家交流互通的平臺(tái)~

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場,如有問題,請(qǐng)聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運(yùn)營商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡稱"軟通動(dòng)力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉