C++移動(dòng)操作,RVO和NRVO詳細(xì)
移動(dòng)操作主要參考了cppreference 的這個(gè)說(shuō)明,
優(yōu)化部分的主要的參考來(lái)自于stack overflow 的這篇文章
一、移動(dòng)操作
1、移動(dòng)操作有關(guān)的函數(shù)
和移動(dòng)操作相關(guān)的類函數(shù)有兩個(gè)
移動(dòng)構(gòu)造函數(shù):
A(A&& rhs);
移動(dòng)賦值運(yùn)算符:
A& operator=(A&& rhs);
注意這兩個(gè)函數(shù)的參數(shù)類型都不是const
,這也是C++默認(rèn)會(huì)生成的函數(shù)聲明。
移動(dòng)構(gòu)造函數(shù)用于在構(gòu)造類型的時(shí)候使用:
A a1; // 使用std::move強(qiáng)制進(jìn)行移動(dòng) A a2 = std::move(a1); 或 A a2(std::move(a1));
而移動(dòng)賦值運(yùn)算符就是在賦值的時(shí)候進(jìn)行移動(dòng):
A a1; A a2; a1 = std::move(a2); // 使用move進(jìn)行強(qiáng)制移動(dòng)
2、何時(shí)自動(dòng)聲明移動(dòng)構(gòu)造函數(shù)和賦值移動(dòng)構(gòu)造函數(shù)
隱式的移動(dòng)構(gòu)造函數(shù)將會(huì)在可以被生成且滿足如下所有條件的情況下自動(dòng)生成:
- 沒(méi)有用戶聲明的 復(fù)制構(gòu)造函數(shù)
- 沒(méi)有用戶聲明的 復(fù)制賦值運(yùn)算符(即
operator
=(const A&)這類) - 沒(méi)有用戶聲明的 移動(dòng)賦值運(yùn)算符(即
operator
=(A&&)這類) - 沒(méi)有用戶聲明的 析構(gòu)函數(shù)
所謂可以被生成的意思是滿足以下所有條件:
- 類中沒(méi)有不能移動(dòng)的非靜態(tài)成員
- 繼承時(shí),基類可以被移動(dòng)
- 繼承時(shí),基類的構(gòu)造函數(shù)可以被訪問(wèn)
而移動(dòng)賦值運(yùn)算符的產(chǎn)生條件也差不多,只不過(guò)將沒(méi)有聲明的 移動(dòng)賦值構(gòu)造函數(shù)改成沒(méi)有用戶聲明 移動(dòng)構(gòu)造函數(shù)即可。
總之,這兩個(gè)函數(shù)生成的條件就一句話:除了普通的構(gòu)造函數(shù)外(指默認(rèn)構(gòu)造函數(shù)和帶其他參數(shù)的構(gòu)造函數(shù)),不得聲明任何其他的構(gòu)造函數(shù),operator
=函數(shù)和析構(gòu)函數(shù)。
3、何時(shí)自動(dòng)移動(dòng)
使用std::move
是一種強(qiáng)制的,顯式的移動(dòng)。但是C++很多時(shí)候?yàn)榱诵蕰?huì)自動(dòng)幫我們移動(dòng)。主要的規(guī)則其實(shí)就是所有的右值都會(huì)進(jìn)行移動(dòng),如果不能移動(dòng),進(jìn)行拷貝。但是為了嚴(yán)謹(jǐn),我們還是擺出cppreference
上的規(guī)則:
- 初始化的時(shí)候使用
std::move():T a = std::move(b)
或者T a(std::move(b));
這種。這里要加上std::move(),
不然會(huì)調(diào)用復(fù)制構(gòu)造函數(shù)。 - 函數(shù)實(shí)參傳遞的時(shí)候使用
std::move() :func(std::move(a))
- 函數(shù)返回時(shí),如:
class A {}; A CreateA() { return A(); } // call A a = CreateA();
的時(shí)候,使用A()產(chǎn)生的變量會(huì)首先移動(dòng)到CreateA()
函數(shù)產(chǎn)生的返回值中,這個(gè)時(shí)候這個(gè)返回值是一個(gè)臨時(shí)變量(我們記為temp
),接下來(lái)就是執(zhí)行這段代碼:A a = temp
,然后temp是臨時(shí)變量, 會(huì)再次調(diào)用A的移動(dòng)構(gòu)造函數(shù)給a變量。
前兩個(gè)是屬于顯式的移動(dòng),最后一種就是隱式移動(dòng)。移動(dòng)賦值運(yùn)算符的規(guī)則也是一樣,只有等號(hào)右邊是臨時(shí)變量就會(huì)自動(dòng)調(diào)用。
二、復(fù)制消除、RVO和NRVO
雖然C++對(duì)移動(dòng)操作定義的很明確,但編譯器卻并不總是按照這個(gè)定義去做。因?yàn)榫幾g器中有三個(gè)重要的優(yōu)化經(jīng)常會(huì)減少拷貝,甚至是移動(dòng)操作。
在GCC
和Clang
下可以添加-fno-elide-constructors
選項(xiàng)來(lái)關(guān)閉這三種優(yōu)化。
1、復(fù)制消除
來(lái)看一看下面代碼:
class C { public: C() {} C(const C&) { std::cout << "A copy was made.\n"; } C(C&& rhs) { std::cout << "A move was made.\n"; } }; C f() { return C(); } int main() { std::cout << "Hello World!\n"; C obj = f(); }
這里建議在C++17標(biāo)準(zhǔn)下編譯,因?yàn)镃++17起所有的復(fù)制消規(guī)則除被寫在語(yǔ)言規(guī)范內(nèi),大部分編譯器應(yīng)該都會(huì)做這件事。我的Clang++ 12.0.5上的執(zhí)行結(jié)果僅僅是輸出了一行Hello World:
Hello World!
按照上面的規(guī)則,函數(shù)在返回的時(shí)候會(huì)進(jìn)行移動(dòng),也就是說(shuō)在f()的調(diào)用內(nèi),會(huì)先移動(dòng)給臨時(shí)變量,然后臨時(shí)變量再移動(dòng)給obj,但是這里什么都沒(méi)發(fā)生,沒(méi)有任何的移動(dòng)和拷貝,obj就像憑空出現(xiàn)了一樣。
在C++17起,復(fù)制消除是強(qiáng)制執(zhí)行的,而C++11中是看編譯器心情。
在如下條件下會(huì)進(jìn)行復(fù)制消除:
- 在
return
語(yǔ)句中,return
的值是和函數(shù)返回值類型一樣的右值。類型一樣是為了防止隱式轉(zhuǎn)換,否則會(huì)產(chǎn)生新的變量從而阻止移動(dòng),右值是因?yàn)镃++自動(dòng)移動(dòng)只能對(duì)右值操作。 - 在變量初始化的時(shí)候,初始化表達(dá)式是右值。如:
class A{}; A f() { return A(); } // 這里是第一種情況,會(huì)自動(dòng)復(fù)制消除 // call A a = f(); // 這里函數(shù)返回值的臨時(shí)變量到a的過(guò)程中的移動(dòng)也會(huì)被消除
這也就解釋了為什么上面的代碼沒(méi)有調(diào)用任何的拷貝,移動(dòng)函數(shù)了。
2、RVO和NRVO
RVO是Return Value Optimization
(返回值優(yōu)化)的簡(jiǎn)寫,而NRVO
是Named Return Value Optimization
(命名返回值優(yōu)化)的簡(jiǎn)寫。這兩個(gè)優(yōu)化是復(fù)制消除的常見(jiàn)形式。
通過(guò)他們的名字就可以看出,這是在函數(shù)返回的時(shí)候做的優(yōu)化。
RVO是指在函數(shù)返回一個(gè)臨時(shí)變量時(shí)的優(yōu)化,具體的優(yōu)化如下:
// 原本的函數(shù) T CreateT(int value) { return T(value); } T a = CreateT(10); // 優(yōu)化后的函數(shù)(偽代碼): void CreateT(T& v, int value) { v.T::T(value); // 直接在內(nèi)部進(jìn)行構(gòu)造 }
即通過(guò)將要接收函數(shù)返回值的對(duì)象以引用的形式放入函數(shù)內(nèi)部初始化,這樣就避免了一次移動(dòng)/拷貝。
而NRVO則是更加寬泛的RVO。對(duì)于如下的代碼可以執(zhí)行NRVO:
T CreateT(int values) { T t(value); return t; }
編譯器也會(huì)優(yōu)化成上面RVO優(yōu)化的樣子。
到此這篇關(guān)于C++移動(dòng)操作,RVO和NRVO詳細(xì)的文章就介紹到這了,更多相關(guān)C++移動(dòng)操作,RVO和NRVO內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++ 實(shí)戰(zhàn)開(kāi)發(fā)一個(gè)猜單詞的小游戲
眾所周知紙上得來(lái)終覺(jué)淺,我們要在實(shí)戰(zhàn)中才能真正的掌握技術(shù),小編為大家?guī)?lái)一份用C++編寫的猜單詞小游戲,給大家練練手,快來(lái)看看吧2021-11-11Qt 添加MSVC2017編譯器的完整教程(保姆級(jí))
本文主要介紹了Qt 添加MSVC2017編譯器的完整教程,文中通過(guò)圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-06-06C語(yǔ)言植物大戰(zhàn)數(shù)據(jù)結(jié)構(gòu)快速排序圖文示例
這篇文章主要為大家介紹了C語(yǔ)言植物大戰(zhàn)數(shù)據(jù)結(jié)構(gòu)快速排序圖文示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05c語(yǔ)言實(shí)現(xiàn)一個(gè)簡(jiǎn)單日歷
本文給大家分享的是一則使用C語(yǔ)言來(lái)實(shí)現(xiàn)的一個(gè)簡(jiǎn)單日歷的代碼,根據(jù)項(xiàng)目需求,實(shí)現(xiàn)了3個(gè)簡(jiǎn)單的小功能,推薦給大家,有需要的小伙伴可以參考下。2015-03-03基于Qt實(shí)現(xiàn)簡(jiǎn)易GIF播放器的示例代碼
這篇文章主要介紹了如何利用Qt設(shè)計(jì)一個(gè)簡(jiǎn)易GIF播放器,可以播放GIF動(dòng)畫(huà)。其基本功能有載入文件、播放、暫停、停止、快進(jìn)和快退,感興趣的可以了解一下2022-06-06