一直以來,C++中基于值語義的拷貝和賦值嚴重影響了程序性能。尤其是對于資源密集型對象,如果進行大量的拷貝,勢必會對程序性能造成很大的影響。
為了盡可能的減小因為對象拷貝對程序的影響,開發(fā)人員使出了萬般招式:盡可能的使用指針、引用。而編譯器也沒閑著,通過使用RVO、NRVO以及復(fù)制省略技術(shù),來減小拷貝次數(shù)來提升代碼的運行效率。
但是,對于開發(fā)人員來說,使用指針和引用不能概括所有的場景,也就是說仍然存在拷貝賦值等行為;對于編譯器來說,而對于RVO、NRVO等編譯器行為的優(yōu)化需要滿足特定的條件(具體可以參考文章編譯器之返回值優(yōu)化)。
為了解決上述問題,自C++11起,引入了移動語義,更進一步對程序性能進行優(yōu)化 。
C++11新標準重新定義了lvalue和rvalue,并允許函數(shù)依照這兩種不同的類型進行重載。通過對于右值(rvalue)的重新定義,語言實現(xiàn)了移動語義(move semantics)和完美轉(zhuǎn)發(fā)(perfect forwarding),通過這種方法,C++實現(xiàn)了在保留原有的語法并不改動已存在的代碼的基礎(chǔ)上提升代碼性能的目的。
本文的主要內(nèi)容如下圖所示:
值語義
值語義(value semantics)指目標對象由源對象拷貝生成,且生成后與源對象完全無關(guān),彼此獨立存在,改變互不影響,就像int類型互相拷貝一樣。C++的內(nèi)置類型(bool/int/double/char)都是值語義,標準庫里的complex<> 、pair<>、vector<>、map<>、string等等類型也都是值語意,拷貝之后就與原對象脫離關(guān)系。
C++中基于值語義的拷貝構(gòu)造和賦值拷貝,會招致對資源密集型對象不必要拷貝,大量的拷貝很可能成為程序的性能瓶頸。
首先,我們看一段例子:
BigObjfun(BigObjobj){
BigObjo;
//dosth
returno;
}
intmain(){
fun(BigObj());
return0;
}
在上述代碼中,我們定義了一個函數(shù)fun()其參數(shù)是一個BigObj對象,當調(diào)用fun()函數(shù)時候,會通過調(diào)用BigObj的拷貝構(gòu)造函數(shù),將obj變量傳遞給fun()的參數(shù)。
編譯器知道何時調(diào)用拷貝構(gòu)造函數(shù)或者賦值運算符進行值傳遞。如果涉及到底層資源,比如內(nèi)存、socket等,開發(fā)人在定義類的時候,需要實現(xiàn)自己的拷貝構(gòu)造和賦值運算符以實現(xiàn)深拷貝
。然而拷貝的代價很大,當我們使用STL容器的時候,都會涉及到大量的拷貝操作,而這些會浪費CPU和內(nèi)存等資源。
正如上述代碼中所示的那樣,當我們將一個臨時變量(BigObj(),也稱為右值)傳遞給一個函數(shù)的時候,就會導致拷貝操作,那么我們該如何避免此種拷貝行為呢?這就是我們本文的主題:移動語義
。
左值、右值
關(guān)于左值、右值,我們在之前的文章中已經(jīng)有過詳細的分享,有興趣的同學可以移步【Modern C++】深入理解左值、右值,在本節(jié),我們簡單介紹下左值和右值的概念,以方便理解下面的內(nèi)容。
左值(lvalue,left value),顧名思義就是賦值符號左邊的值。準確來說,左值是表達式結(jié)束(不一定是賦值表達式)后依然存在的對象。
可以將左值看作是一個關(guān)聯(lián)了名稱的內(nèi)存位置,允許程序的其他部分來訪問它。在這里,我們將 "名稱" 解釋為任何可用于訪問內(nèi)存位置的表達式。所以,如果 arr 是一個數(shù)組,那么 arr[1] 和 *(arr+1) 都將被視為相同內(nèi)存位置的“名稱”。
左值具有以下特征:
- 可通過取地址運算符獲取其地址
- 可修改的左值可用作內(nèi)建賦值和內(nèi)建符合賦值運算符的左操作數(shù)
- 可以用來初始化左值引用(后面有講)
C++11將右值分為純右值和將亡值兩種。純右值就是C++98標準中右值的概念,如非引用返回的函數(shù)返回的臨時變量值;一些運算表達式,如1+2產(chǎn)生的臨時變量;不跟對象關(guān)聯(lián)的字面量值,如2,'c',true,"hello";這些值都不能夠被取地址。
而將亡值則是C++11新增的和右值引用相關(guān)的表達式,這樣的表達式通常是將要移動的對象、T&&函數(shù)返回值、std::move()函數(shù)的返回值等。
左值引用、右值引用
在明確了左值和右值的概念之后,我們將在本節(jié)簡單介紹下左值引用和右值引用。
按照概念,對左值的引用稱為左值引用,而對右值的引用稱為右值引用。既然有了左值引用和右值引用,那么在C++11之前,我們通常所說的引用又是什么呢?其實就是左值引用,比如:
inta=1;
int&b=a;
在C++11之前,我們通過會說b是對a的一個引用(當然,在C++11及以后也可以這么說,大家潛移默化的認識就是引用==左值引用),但是在C++11中,更為精確的說法是b是一個左值引用。
在C++11中,為了區(qū)分左值引用,右值引用用&&來表示,如下:
int&&a=1;//a是一個左值引用
intb=1;
int&&c=b;//錯誤,右值引用不能綁定左值
跟左值引用一樣,右值引用不會發(fā)生拷貝,并且右值引用等號右邊必須是右值,如果是左值則會編譯出錯,當然這里也可以進行強制轉(zhuǎn)換,這將在后面提到。
在這里,有一個大家都經(jīng)常容易犯的一個錯誤,就是綁定右值的右值引用,其變量本身是個左值。為了便于理解,代碼如下:
intfun(int&a){
std::cout<"infun(int&)"<std::endl;
}
intfun(int&&a){
std::cout<"infun(int&)"<std::endl;
}
intmain(){
inta=1;
int&&b=1;
fun(b);
return0;
}
代碼輸出如下:
infun(int&)
左值引用和右值引用的規(guī)則如下:
- 左值引用,使用T&,只能綁定左值
- 右值引用,使用T&&,只能綁定右值
- 常量左值,使用const T&,既可以綁定左值,又可以綁定右值,但是不能對其進行修改
- 具名右值引用,編譯器會認為是個左值
- 編譯器的優(yōu)化需要滿足特定條件,不能過度依賴
好了,截止到目前,相信你對左值引用和右值引用的概念有了初步的認識,那么,現(xiàn)在我們介紹下為什么要有右值引用呢?我們看下述代碼:
BigObjfun(){
returnBigObj();
}
BigObjobj=fun();//C++11以前
BigObj&&obj=fun();//C++11
上述代碼中,在C++11之前,我們只能通過編譯器優(yōu)化(N)RVO的方式來提升性能,如果不滿足編譯器的優(yōu)化條件,則只能通過拷貝等方式進行操作。自C++11引入右值引用后,對于不滿足(N)RVO條件,也可以通過避免拷貝延長臨時變量的生命周期,進而達到優(yōu)化的目的。
但是僅僅使用右值引用還不足以完全達到優(yōu)化目的,畢竟右值引用只能綁定右值。那么,對于左值,我們又該如何優(yōu)化呢?是否可以通過左值轉(zhuǎn)成右值,然后進行優(yōu)化呢?等等
為了解決上述問題,標準引入了移動語義。通移動語義,可以在必要的時候避免拷貝;標準提供了move()函數(shù),可以將左值轉(zhuǎn)換成右值。接下來,就開始我們本文的重點-移動語義。
移動語義
移動語義是Howard Hinnant在2002年向C++標準委員會提議的,引用其在移動語義提案上的一句話:
移動語義不是試圖取代復(fù)制語義,也不是以任何方式破壞它。相反,該提議旨在增強復(fù)制語義
對于剛剛接觸移動語義的開發(fā)人員來說,很難理解為什么有了值語義還需要有移動語義。
我們可以想象一下,有一輛汽車,在內(nèi)置發(fā)動機的情況下運行平穩(wěn),有一天,在這輛車上安裝了一個額外的V8發(fā)動機。當有足夠燃料的時候,V8發(fā)動機就能進行加速。所以,汽車是值語義,而V8引擎則是移動語義。在車上安裝引擎不需要一輛新車,它仍然是同一輛車,就像移動語義不會放棄值語義一樣。
所以,如果可以,使用移動語義,否則使用值語義,換句話說就是,如果燃料充足,則使用V8引擎,否則使用原始默認引擎。
好了,截止到現(xiàn)在,我們對移動語義有一個感官上的認識,它屬于一種優(yōu)化,或者說屬于錦上添花。再次引用Howard Hinnant在移動語義提案上的一句話:
移動語義主要是性能優(yōu)化:將昂貴的對象從內(nèi)存中的一個地址移動到另外一個地址的能力,同時竊取源資源以便以最小的代價構(gòu)建目標
在C++11之前,當進行值傳遞時,編譯器會隱式調(diào)用拷貝構(gòu)造函數(shù);自C++11起,通過右值引用來避免由于拷貝調(diào)用而導致的性能損失。
右值引用的主要用途是創(chuàng)建移動構(gòu)造函數(shù)和移動賦值運算符
。移動構(gòu)造函數(shù)和拷貝構(gòu)造函數(shù)一樣,將對象的實例作為其參數(shù),并從原始對象創(chuàng)建一個新的實例。
但是,移動構(gòu)造函數(shù)可以避免內(nèi)存重新分配
,這是因為移動構(gòu)造函數(shù)的參數(shù)是一個右值引用,也可以說是一個臨時對象
,而臨時對象在調(diào)用之后就被銷毀不再被使用,因此,在移動構(gòu)造函數(shù)中對參數(shù)進行移動而不是拷貝。換句話說,右值引用和移動語義允許我們在使用臨時對象時避免不必要的拷貝。
移動語義通過移動構(gòu)造函數(shù)和移動賦值操作符實現(xiàn),其與拷貝構(gòu)造函數(shù)類似,區(qū)別如下:
- 參數(shù)的符號必須為右值引用符號,即為&&
- 參數(shù)不可以是常量,因為函數(shù)內(nèi)需要修改參數(shù)的值
- 參數(shù)的成員轉(zhuǎn)移后需要修改(如改為nullptr),避免臨時對象的析構(gòu)函數(shù)將資源釋放掉
為了方便我們理解,下面代碼包含了完整的移動構(gòu)造和移動運算符,如下:
classBigObj{
public:
explicitBigObj(size_tlength)
:length_(length),data_(newint[length]){
}
//Destructor.
~BigObj(){
if(data_!=NULL){
delete[]data_;
length_=0;
}
}
//拷貝構(gòu)造函數(shù)
BigObj(constBigObj&other)
:length_(other.length_),data(newint[other.length_]){
std::copy(other.mData,other.mData+mLength,mData);
}
//賦值運算符
BigObj&operator=(constBigObj&other){
if(this!=&other;){
delete[]data_;
length_=other.length_;
data_=newint[length_];
std::copy(other.data_,other.data_+length_,data_);
}
return*this;
}
//移動構(gòu)造函數(shù)
BigObj(BigObj&&other):data_(nullptr),length_(0){
data_=other.data_;
length_=other.length_;
other.data_=nullptr;
other.length_=0;
}
//移動賦值運算符
BigObj&operator=(BigObj&&other){
if(this!=&other;){
delete[]data_;
data_=other.data_;
length_=other.length_;
other.data_=NULL;
other.length_=0;
}
return*this;
}
private:
size_tlength_;
int*data_;
};
intmain(){
std::vectorv;
v.push_back(BigObj(25));
v.push_back(BigObj(75));
v.insert(v.begin()+1,BigObj(50));
return0;
}
移動構(gòu)造
移動構(gòu)造函數(shù)的定義如下:
BigObj(BigObj&&other):data_(nullptr),length_(0){
data_=other.data_;
length_=other.length_;
other.data_=nullptr;
other.length_=0;
}
從上述代碼可以看出,它不分配任何新資源,也不會復(fù)制其它資源:other中的內(nèi)存被移動
到新成員后,other中原有的內(nèi)容則消失了。換句話說,它竊取
了other的資源,然后將other設(shè)置為其默認構(gòu)造的狀態(tài)。
在移動構(gòu)造函數(shù)中,最最關(guān)鍵的一點是,它沒有額外的資源分配,僅僅是將其它對象的資源進行了移動,占為己用。
在此,我們假設(shè)data_很大,包含了數(shù)百萬個元素。如果使用原來拷貝構(gòu)造函數(shù)的話,就需要將該數(shù)百萬元素挨個進行復(fù)制,性能可想而知。而如果使用該移動構(gòu)造函數(shù),因為不涉及到新資源的創(chuàng)建,不僅可以節(jié)省很多資源,而且性能也有很大的提升。
移動賦值運算符
代碼如下:
BigObj&operator=(constBigObj&other){
if(this!=&other;){
delete[]data_;
length_=other.length_;
data_=newint[length_];
std::copy(other.data_,other.data_+length_,data_);
}
return*this;
}
移動賦值運算符的寫法類似于拷貝賦值運算符,所不同點在于:移動賦值預(yù)算法會破壞被操作的對象(上述代碼中的參數(shù)other)。
移動賦值運算符的操作步驟如下:
- 釋放當前擁有的資源
- 竊取他人資源
- 將他人資源設(shè)置為默認狀態(tài)
- 返回*this
在定義移動賦值運算符的時候,需要進行判斷,即被移動的對象是否跟目標對象一致,如果一致,則會出問題,如下代碼:
data=std::move(data);
在上述代碼中,源和目標是同一個對象,這可能會導致一個嚴重的問題:它最終可能會釋放它試圖移動的資源。為了避免此問題,我們需要通過判斷來進行,比如可以如下操作:
if(this==&other){
return*this
}
生成時機
眾所周知,在C++中有四個特殊的成員函數(shù):默認構(gòu)造函數(shù)、析構(gòu)函數(shù),拷貝構(gòu)造函數(shù),拷貝賦值運算符。
之所以稱之為特殊的成員函數(shù),這是因為如何開發(fā)人員沒有定義這四個成員函數(shù),那么編譯器則在滿足某些特定條件(僅在需要的時候才生成,比如某個代碼使用它們但是它們沒有在類中明確聲明)下,自動生成。這些由編譯器生成的特殊成員函數(shù)是public且inline。
自C++11起,引入了另外兩只特殊的成員函數(shù):移動構(gòu)造函數(shù)和移動賦值運算符。如果開發(fā)人員沒有顯示定義移動構(gòu)造函數(shù)和移動賦值運算符,那么編譯器也會生成默認。
與其他四個特殊成員函數(shù)不同,編譯器生成默認的移動構(gòu)造函數(shù)和移動賦值運算符需要,滿足以下條件:
- 如果一個類定義了自己的拷貝構(gòu)造函數(shù),拷貝賦值運算符或者析構(gòu)函數(shù)(這三者之一,表示程序員要自己處理對象的復(fù)制或釋放問題),編譯器就不會為它生成默認的移動構(gòu)造函數(shù)或者移動賦值運算符,這樣做的目的是防止編譯器生成的默認移動構(gòu)造函數(shù)或者移動賦值運算符不是開發(fā)人員想要的
- 如果類中沒有提供移動構(gòu)造函數(shù)和移動賦值運算符,且編譯器不會生成默認的,那么我們在代碼中通過std::move()調(diào)用的移動構(gòu)造或者移動賦值的行為將被轉(zhuǎn)換為調(diào)用拷貝構(gòu)造或者賦值運算符
- 只有一個類沒有顯示定義拷貝構(gòu)造函數(shù)、賦值運算符以及析構(gòu)函數(shù),且類的每個非靜態(tài)成員都可以移動時,編譯器才會生成默認的移動構(gòu)造函數(shù)或者移動賦值運算符
- 如果顯式聲明了移動構(gòu)造函數(shù)或移動賦值運算符,則拷貝構(gòu)造函數(shù)和拷貝賦值運算符將被隱式刪除(因此程開發(fā)人員必須在需要時實現(xiàn)拷貝構(gòu)造函數(shù)和拷貝賦值運算符)
與拷貝操作一樣,如果開發(fā)人員定義了移動操作,那么編譯器就不會生成默認的移動操作,但是編譯器生成移動操作的行為和生成拷貝操作的行為有些許不同,如下:
-
兩個拷貝操作是獨立的:聲明一個不會限制編譯器生成另一個。所以如果你聲明一個拷貝構(gòu)造函數(shù),但是沒有聲明拷貝賦值運算符,如果寫的代碼用到了拷貝賦值,編譯器會幫助你生成拷貝賦值運算符。
同樣的,如果你聲明拷貝賦值運算符但是沒有拷貝構(gòu)造函數(shù),代碼用到拷貝構(gòu)造函數(shù)時編譯器就會生成它。上述規(guī)則在C++98和C++11中都成立。
-
兩個移動操作不是相互獨立的。如果你聲明了其中一個,編譯器就不再生成另一個。如果你給類聲明了,比如,一個移動構(gòu)造函數(shù),就表明對于移動操作應(yīng)怎樣實現(xiàn),與編譯器應(yīng)生成的默認逐成員移動有些區(qū)別。
如果逐成員移動構(gòu)造有些問題,那么逐成員移動賦值同樣也可能有問題。所以聲明移動構(gòu)造函數(shù)阻止編譯器生成移動賦值運算符,聲明移動賦值運算符同樣阻止編譯器生成移動構(gòu)造函數(shù)。
類型轉(zhuǎn)換-move()函數(shù)
在前面的文章中,我們提到,如果需要調(diào)用移動構(gòu)造函數(shù)和移動賦值運算符,就需要用到右值。那么,對于一個左值,又如何使用移動語義呢?自C++11起,標準庫提供了一個函數(shù)move()用于將左值轉(zhuǎn)換成右值。
首先,我們看下cppreference中對move語義的定義:
std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object.
In particular, std::move produces an xvalue expression that identifies its argument t. It is exactly equivalent to a static_cast to an rvalue reference type.
從上述描述,我們可以理解為std::move()
并沒有移動
任何東西,它只是進行類型轉(zhuǎn)換而已,真正進行資源轉(zhuǎn)移的是開發(fā)人員實現(xiàn)的移動操作
。
該函數(shù)在STL中定義如下:
template<typename_Tp>
constexprtypenamestd::remove_reference<_Tp>::type&&
move(_Tp&&__t)noexcept
{returnstatic_cast<typenamestd::remove_reference<_Tp>::type&&>(__t);}
從上面定義可以看出,std::move()并不是什么黑魔法,而只是進行了簡單的類型轉(zhuǎn)換:
- 如果傳遞的是左值,則推導為左值引用,然后由static_cast轉(zhuǎn)換為右值引用
- 如果傳遞的是右值,則推導為右值引用,然后static_cast轉(zhuǎn)換為右值引用
使用move之后,就意味著兩點:
- 原對象不再被使用,如果對其使用會造成不可預(yù)知的后果
- 所有權(quán)轉(zhuǎn)移,資源的所有權(quán)被轉(zhuǎn)移給新的對象
使用
在某些情況下,編譯器會嘗試隱式移動,這意味著您不必使用std::move()
。只有當一個非常量的可移動對象被傳遞、返回或賦值,并且即將被自動銷毀時,才會發(fā)生這種情況。
自c++11起,開始支持右值引用。標準庫中很多容器都支持移動語義,以std::vector<>為例,**vector::push_back()**定義了兩個重載版本,一個像以前一樣將const T&用于左值參數(shù),另一個將T&&類型的參數(shù)用于右值參數(shù)。如下代碼:
intmain(){
std::vectorv;
v.push_back(BigObj(10));
v.push_back(BigObj(20));
return0;
}
兩個push_back()調(diào)用都將解析為push_back(T&&),因為它們的參數(shù)是右值。push_back(T&&)使用BigObj的移動構(gòu)造函數(shù)將資源從參數(shù)移動到vector的內(nèi)部BigObj對象中。而在C++11之前,上述代碼則生成參數(shù)的拷貝,然后調(diào)用BigObj的拷貝構(gòu)造函數(shù)。
如果參數(shù)是左值,則將調(diào)用push_back(T&):
intmain(){
std::vectorv;
BigObjobj(10);
v.push_back(obj);//此處調(diào)用push_back(T&)
return0;
}
對于左值對象,如果我們想要避免拷貝操作,則可以使用標準庫提供的move()函數(shù)來實現(xiàn)(前提是類定義中實現(xiàn)了移動語義),代碼如下:
intmain(){
std::vectorv;
BigObjobj(10);
v.push_back(std::move(obj));//此處調(diào)用push_back(T&&)
return0;
}
我們再看一個常用的函數(shù)swap(),在使用移動構(gòu)造之前,我們定義如下:
template
voidswap(T&a,T&b) {
Ttemp=a;//調(diào)用拷貝構(gòu)造函數(shù)
a=b;//調(diào)用operator=
b=temp;//調(diào)用operator=
}
如果T是簡單類型,則上述轉(zhuǎn)換沒有問題。但如果T是含有指針的復(fù)合數(shù)據(jù)類型,則上述轉(zhuǎn)換中會調(diào)用一次復(fù)制構(gòu)造函數(shù),兩次賦值運算符重載。
而如果使用move()函數(shù)后,則代碼如下:
template
voidswap(T&a,T&b) {
Ttemp=std::move(a);
a=std::move(b);
b=std::move(temp);
}
與傳統(tǒng)的swap實現(xiàn)相比,使用move()函數(shù)的swap()版本減少了拷貝等操作。如果T是可移動的,那么整個操作將非常高效。如果它是不可移動的,那么它和普通的swap函數(shù)一樣,調(diào)用拷貝和賦值操作,不會出錯,且是安全可靠的。
經(jīng)驗之談
對int等基礎(chǔ)類型進行move()操作,不會改變其原值
對于所有的基礎(chǔ)類型-int、double、指針以及其它類型,它們本身不支持移動操作(也可以說本身沒有實現(xiàn)移動語義,畢竟不屬于我們通常理解的對象嘛),所以,對于這些基礎(chǔ)類型進行move()操作,最終還是會調(diào)用拷貝行為,代碼如下:
intmain()
{
inta=1;
int&&b=std::move(a);
std::cout<"a="<std::endl;
std::cout<"b="<std::endl;
return0;
}
最終結(jié)果輸出如下:
a=1
b=1
move構(gòu)造或者賦值函數(shù)中,請將原對象恢復(fù)默認值
我們看如下代碼:
classBigObj{
public:
explicitBigObj(size_tlength)
:length_(length),data_(newint[length]){
}
//Destructor.
~BigObj(){
if(data_!=NULL){
delete[]data_;
length_=0;
}
}
//拷貝構(gòu)造函數(shù)
BigObj(constBigObj&other)=default;
//賦值運算符
BigObj&operator=(constBigObj&other)=default;
//移動構(gòu)造函數(shù)
BigObj(BigObj&&other):data_(nullptr),length_(0){
data_=other.data_;
length_=other.length_;
}
private:
size_tlength_;
int*data_;
};
intmain(){
BigObjobj(1000);
BigObjo;
{
o=std::move(obj);
}
//useobj;
return0;
}
在上述代碼中,調(diào)用移動構(gòu)造函數(shù)后,沒有將原對象回復(fù)默認值,導致目標對象和原對象的底層資源(data_)執(zhí)行同一個內(nèi)存塊,這樣就導致退出main()函數(shù)的時候,原對象和目標對象均調(diào)用析構(gòu)函數(shù)釋放同一個內(nèi)存塊,進而導致程序崩潰。
不要在函數(shù)中使用std::move()進行返回
我們?nèi)匀灰設(shè)bj進行舉例,代碼如下:
Objfun(){
Objobj;
returnstd::move(obj);
}
intmain(){
Objo1=fun();
return0;
}
程序輸出:
inObj()0x7ffe600d79e0
inObj(constObj&&obj)
in~Obj()0x7ffe600d79e0
如果把fun()函數(shù)中的std::move(obj)換成return obj,則輸出如下:
inObj()0x7ffcfefaa750
通過上述示例的輸出,是不是有點超出我們的預(yù)期。從輸出可以看出來,第二種方式(直接return obj)比第一種方式少了一次move構(gòu)造和析構(gòu)。這是因為編譯器做了NRVO優(yōu)化。
所以,我們需要切記:如果編譯器能夠?qū)δ硞€函數(shù)做(N)RVO優(yōu)化,就使用(N)RVO,而不是自作聰明使用std::move()。
知己知彼
STL中大部分已經(jīng)實現(xiàn)移動語義,比如std::vector<>,std::map<>等,同時std::unique_ptr<>等不能被拷貝的類也支持移動語義。
我們看下如下代碼:
classBigObj
{
public:
BigObj(){
std::cout<<__PRETTY_FUNCTION__<<std::endl;
}
~BigObj(){
std::cout<<__PRETTY_FUNCTION__<<std::endl;
}
BigObj(constBigObj&b){
std::cout<<__PRETTY_FUNCTION__<<std::endl;
}
BigObj(BigObj&&b){
std::cout<<__PRETTY_FUNCTION__<<std::endl;
}
};
intmain(){
std::arrayv;
autov1=std::move(v);
return0;
}
上述代碼輸出如下:
BigObj::BigObj()
BigObj::BigObj()
BigObj::BigObj(BigObj&&)
BigObj::BigObj(BigObj&&)
BigObj::~BigObj()
BigObj::~BigObj()
BigObj::~BigObj()
BigObj::~BigObj()
而如果把main()函數(shù)中的std::array<>換成std::vector<>后,如下:
intmain(){
std::vectorv;
v.resize(2);
autov1=std::move(v);
return0;
}
則輸出如下:
BigObj::BigObj()
BigObj::BigObj()
BigObj::~BigObj()
BigObj::~BigObj()
從上述兩處輸出可以看出,std::vector<>對應(yīng)的移動構(gòu)造不會生成多余的構(gòu)造,且原本的element都移動到v1中;而相比std::array<>中對應(yīng)的移動構(gòu)造卻有很大的區(qū)別,基本上會對每個element都調(diào)用移動構(gòu)造函數(shù)而不是對std::array<>本身。
因此,在使用std::move()的時候,最好要知道底層的基本實現(xiàn)原理,否則往往會得到我們意想不到的結(jié)果。
原文標題:【Modern C++】深入理解移動語義
文章出處:【微信公眾號:Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
-
C++
+關(guān)注
關(guān)注
21文章
2102瀏覽量
73457 -
代碼
+關(guān)注
關(guān)注
30文章
4728瀏覽量
68250 -
語義
+關(guān)注
關(guān)注
0文章
21瀏覽量
8656
原文標題:【Modern C++】深入理解移動語義
文章出處:【微信號:LinuxHub,微信公眾號:Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論