C++移動操作,RVO和NRVO詳細(xì)
移動操作主要參考了cppreference 的這個說明,
優(yōu)化部分的主要的參考來自于stack overflow 的這篇文章
一、移動操作
1、移動操作有關(guān)的函數(shù)
和移動操作相關(guān)的類函數(shù)有兩個
移動構(gòu)造函數(shù):
A(A&& rhs);
移動賦值運算符:
A& operator=(A&& rhs);
注意這兩個函數(shù)的參數(shù)類型都不是const
,這也是C++默認(rèn)會生成的函數(shù)聲明。
移動構(gòu)造函數(shù)用于在構(gòu)造類型的時候使用:
A a1; // 使用std::move強(qiáng)制進(jìn)行移動 A a2 = std::move(a1); 或 A a2(std::move(a1));
而移動賦值運算符就是在賦值的時候進(jìn)行移動:
A a1; A a2; a1 = std::move(a2); // 使用move進(jìn)行強(qiáng)制移動
2、何時自動聲明移動構(gòu)造函數(shù)和賦值移動構(gòu)造函數(shù)
隱式的移動構(gòu)造函數(shù)將會在可以被生成且滿足如下所有條件的情況下自動生成:
- 沒有用戶聲明的 復(fù)制構(gòu)造函數(shù)
- 沒有用戶聲明的 復(fù)制賦值運算符(即
operator
=(const A&)這類) - 沒有用戶聲明的 移動賦值運算符(即
operator
=(A&&)這類) - 沒有用戶聲明的 析構(gòu)函數(shù)
所謂可以被生成的意思是滿足以下所有條件:
- 類中沒有不能移動的非靜態(tài)成員
- 繼承時,基類可以被移動
- 繼承時,基類的構(gòu)造函數(shù)可以被訪問
而移動賦值運算符的產(chǎn)生條件也差不多,只不過將沒有聲明的 移動賦值構(gòu)造函數(shù)改成沒有用戶聲明 移動構(gòu)造函數(shù)即可。
總之,這兩個函數(shù)生成的條件就一句話:除了普通的構(gòu)造函數(shù)外(指默認(rèn)構(gòu)造函數(shù)和帶其他參數(shù)的構(gòu)造函數(shù)),不得聲明任何其他的構(gòu)造函數(shù),operator
=函數(shù)和析構(gòu)函數(shù)。
3、何時自動移動
使用std::move
是一種強(qiáng)制的,顯式的移動。但是C++很多時候為了效率會自動幫我們移動。主要的規(guī)則其實就是所有的右值都會進(jìn)行移動,如果不能移動,進(jìn)行拷貝。但是為了嚴(yán)謹(jǐn),我們還是擺出cppreference
上的規(guī)則:
- 初始化的時候使用
std::move():T a = std::move(b)
或者T a(std::move(b));
這種。這里要加上std::move(),
不然會調(diào)用復(fù)制構(gòu)造函數(shù)。 - 函數(shù)實參傳遞的時候使用
std::move() :func(std::move(a))
- 函數(shù)返回時,如:
class A {}; A CreateA() { return A(); } // call A a = CreateA();
的時候,使用A()產(chǎn)生的變量會首先移動到CreateA()
函數(shù)產(chǎn)生的返回值中,這個時候這個返回值是一個臨時變量(我們記為temp
),接下來就是執(zhí)行這段代碼:A a = temp
,然后temp是臨時變量, 會再次調(diào)用A的移動構(gòu)造函數(shù)給a變量。
前兩個是屬于顯式的移動,最后一種就是隱式移動。移動賦值運算符的規(guī)則也是一樣,只有等號右邊是臨時變量就會自動調(diào)用。
二、復(fù)制消除、RVO和NRVO
雖然C++對移動操作定義的很明確,但編譯器卻并不總是按照這個定義去做。因為編譯器中有三個重要的優(yōu)化經(jīng)常會減少拷貝,甚至是移動操作。
在GCC
和Clang
下可以添加-fno-elide-constructors
選項來關(guān)閉這三種優(yōu)化。
1、復(fù)制消除
來看一看下面代碼:
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)下編譯,因為C++17起所有的復(fù)制消規(guī)則除被寫在語言規(guī)范內(nèi),大部分編譯器應(yīng)該都會做這件事。我的Clang++ 12.0.5上的執(zhí)行結(jié)果僅僅是輸出了一行Hello World:
Hello World!
按照上面的規(guī)則,函數(shù)在返回的時候會進(jìn)行移動,也就是說在f()的調(diào)用內(nèi),會先移動給臨時變量,然后臨時變量再移動給obj,但是這里什么都沒發(fā)生,沒有任何的移動和拷貝,obj就像憑空出現(xiàn)了一樣。
在C++17起,復(fù)制消除是強(qiáng)制執(zhí)行的,而C++11中是看編譯器心情。
在如下條件下會進(jìn)行復(fù)制消除:
- 在
return
語句中,return
的值是和函數(shù)返回值類型一樣的右值。類型一樣是為了防止隱式轉(zhuǎn)換,否則會產(chǎn)生新的變量從而阻止移動,右值是因為C++自動移動只能對右值操作。 - 在變量初始化的時候,初始化表達(dá)式是右值。如:
class A{}; A f() { return A(); } // 這里是第一種情況,會自動復(fù)制消除 // call A a = f(); // 這里函數(shù)返回值的臨時變量到a的過程中的移動也會被消除
這也就解釋了為什么上面的代碼沒有調(diào)用任何的拷貝,移動函數(shù)了。
2、RVO和NRVO
RVO是Return Value Optimization
(返回值優(yōu)化)的簡寫,而NRVO
是Named Return Value Optimization
(命名返回值優(yōu)化)的簡寫。這兩個優(yōu)化是復(fù)制消除的常見形式。
通過他們的名字就可以看出,這是在函數(shù)返回的時候做的優(yōu)化。
RVO是指在函數(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)造 }
即通過將要接收函數(shù)返回值的對象以引用的形式放入函數(shù)內(nèi)部初始化,這樣就避免了一次移動/拷貝。
而NRVO則是更加寬泛的RVO。對于如下的代碼可以執(zhí)行NRVO:
T CreateT(int values) { T t(value); return t; }
編譯器也會優(yōu)化成上面RVO優(yōu)化的樣子。
到此這篇關(guān)于C++移動操作,RVO和NRVO詳細(xì)的文章就介紹到這了,更多相關(guān)C++移動操作,RVO和NRVO內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言植物大戰(zhàn)數(shù)據(jù)結(jié)構(gòu)快速排序圖文示例
這篇文章主要為大家介紹了C語言植物大戰(zhàn)數(shù)據(jù)結(jié)構(gòu)快速排序圖文示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05