C++移動(dòng)操作,RVO和NRVO詳細(xì)
移動(dòng)操作主要參考了cppreference 的這個(gè)說明,
優(yōu)化部分的主要的參考來自于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)會生成的函數(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ù)將會在可以被生成且滿足如下所有條件的情況下自動(dòng)生成:
- 沒有用戶聲明的 復(fù)制構(gòu)造函數(shù)
- 沒有用戶聲明的 復(fù)制賦值運(yùn)算符(即
operator=(const A&)這類) - 沒有用戶聲明的 移動(dòng)賦值運(yùn)算符(即
operator=(A&&)這類) - 沒有用戶聲明的 析構(gòu)函數(shù)
所謂可以被生成的意思是滿足以下所有條件:
- 類中沒有不能移動(dòng)的非靜態(tài)成員
- 繼承時(shí),基類可以被移動(dòng)
- 繼承時(shí),基類的構(gòu)造函數(shù)可以被訪問
而移動(dòng)賦值運(yùn)算符的產(chǎn)生條件也差不多,只不過將沒有聲明的 移動(dòng)賦值構(gòu)造函數(shù)改成沒有用戶聲明 移動(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)榱诵蕰詣?dòng)幫我們移動(dòng)。主要的規(guī)則其實(shí)就是所有的右值都會進(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(),不然會調(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)生的變量會首先移動(dòng)到CreateA()函數(shù)產(chǎn)生的返回值中,這個(gè)時(shí)候這個(gè)返回值是一個(gè)臨時(shí)變量(我們記為temp),接下來就是執(zhí)行這段代碼:A a = temp,然后temp是臨時(shí)變量, 會再次調(diào)用A的移動(dòng)構(gòu)造函數(shù)給a變量。
前兩個(gè)是屬于顯式的移動(dòng),最后一種就是隱式移動(dòng)。移動(dòng)賦值運(yùn)算符的規(guī)則也是一樣,只有等號右邊是臨時(shí)變量就會自動(dòng)調(diào)用。
二、復(fù)制消除、RVO和NRVO
雖然C++對移動(dòng)操作定義的很明確,但編譯器卻并不總是按照這個(gè)定義去做。因?yàn)榫幾g器中有三個(gè)重要的優(yōu)化經(jīng)常會減少拷貝,甚至是移動(dòng)操作。
在GCC和Clang下可以添加-fno-elide-constructors選項(xiàng)來關(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)下編譯,因?yàn)镃++17起所有的復(fù)制消規(guī)則除被寫在語言規(guī)范內(nèi),大部分編譯器應(yīng)該都會做這件事。我的Clang++ 12.0.5上的執(zhí)行結(jié)果僅僅是輸出了一行Hello World:
Hello World!
按照上面的規(guī)則,函數(shù)在返回的時(shí)候會進(jìn)行移動(dòng),也就是說在f()的調(diào)用內(nèi),會先移動(dòng)給臨時(shí)變量,然后臨時(shí)變量再移動(dòng)給obj,但是這里什么都沒發(fā)生,沒有任何的移動(dòng)和拷貝,obj就像憑空出現(xiàn)了一樣。
在C++17起,復(fù)制消除是強(qiáng)制執(zhí)行的,而C++11中是看編譯器心情。
在如下條件下會進(jìn)行復(fù)制消除:
- 在
return語句中,return的值是和函數(shù)返回值類型一樣的右值。類型一樣是為了防止隱式轉(zhuǎn)換,否則會產(chǎn)生新的變量從而阻止移動(dòng),右值是因?yàn)镃++自動(dòng)移動(dòng)只能對右值操作。 - 在變量初始化的時(shí)候,初始化表達(dá)式是右值。如:
class A{};
A f() { return A(); } // 這里是第一種情況,會自動(dòng)復(fù)制消除
// call
A a = f(); // 這里函數(shù)返回值的臨時(shí)變量到a的過程中的移動(dòng)也會被消除
這也就解釋了為什么上面的代碼沒有調(diào)用任何的拷貝,移動(dòng)函數(shù)了。
2、RVO和NRVO
RVO是Return Value Optimization(返回值優(yōu)化)的簡寫,而NRVO是Named Return Value Optimization(命名返回值優(yōu)化)的簡寫。這兩個(gè)優(yōu)化是復(fù)制消除的常見形式。
通過他們的名字就可以看出,這是在函數(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)造
}
即通過將要接收函數(shù)返回值的對象以引用的形式放入函數(shù)內(nèi)部初始化,這樣就避免了一次移動(dòng)/拷貝。
而NRVO則是更加寬泛的RVO。對于如下的代碼可以執(zhí)行NRVO:
T CreateT(int values) {
T t(value);
return t;
}
編譯器也會優(yōu)化成上面RVO優(yōu)化的樣子。
到此這篇關(guān)于C++移動(dòng)操作,RVO和NRVO詳細(xì)的文章就介紹到這了,更多相關(guān)C++移動(dòng)操作,RVO和NRVO內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解C++語言中的加法運(yùn)算符與賦值運(yùn)算符的用法
這篇文章主要介紹了C++語言中的加法運(yùn)算符與賦值運(yùn)算符的用法,是C++入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下2016-01-01
Linux?C/C++?timeout命令實(shí)現(xiàn)運(yùn)行具有時(shí)間限制功能
inux?timeout命令的一個(gè)屬性是時(shí)間限制??梢詾槿魏蚊钤O(shè)置時(shí)間限制。如果時(shí)間到期,命令將停止執(zhí)行,這篇文章主要介紹了Linux?C/C++?timeout命令實(shí)現(xiàn)(運(yùn)行具有時(shí)間限制),需要的朋友可以參考下2023-02-02
gazebo里通過節(jié)點(diǎn)發(fā)布topic讓關(guān)節(jié)轉(zhuǎn)動(dòng)實(shí)現(xiàn)詳解
這篇文章主要介紹了gazebo里通過節(jié)點(diǎn)發(fā)布topic讓關(guān)節(jié)轉(zhuǎn)動(dòng)實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
C語言實(shí)現(xiàn)隨機(jī)生成6位數(shù)密碼
這篇文章主要為大家詳細(xì)介紹了如何使用C語言實(shí)現(xiàn)一個(gè)簡單而實(shí)用的隨機(jī)密碼生成器,該生成器將生成包含字母、數(shù)字和特殊字符的隨機(jī)密碼,有需要的小伙伴可以參考下2023-11-11

