C++11的版本在vector容器添加了emplace_back方法,相對于原先的push_back方法能夠在一定程度上提升vector容器的表現(xiàn)性能。所以我們從STL源碼角度來切入,看看這兩種方法有什么樣的區(qū)別,新引進(jìn)的方法又有什么可學(xué)習(xí)參考之處。
1.emplace_back的用法emplace_back方法最大的改進(jìn)就在與可以利用類本身的構(gòu)造函數(shù)直接在內(nèi)存之中構(gòu)建對象,而不需要調(diào)用類的拷貝構(gòu)造函數(shù)與移動構(gòu)造函數(shù)。舉個栗子,假設(shè)如下定義了一個時間類time,該類同時定義了拷貝構(gòu)造函數(shù)與移動構(gòu)造函數(shù):
class time {
private:
?int hour;?
?int minute;?
?int second;
public:?
?time(int h, int m, int s) :hour(h), minute(m), second(s) {?
?}?
?time(const time& t) :hour(t.hour), minute(t.minute), second(t.second) {?
?cout << "copy" << endl;?
?}?
?time(const time&& t) noexcept:hour(t.hour),minute(t.minute),second(t.second) {
?cout << "move" << endl;
?}
};
在main方法之中執(zhí)行下面的代碼邏輯:
intmain(){
? ? vector tlist;
? ? timet(1,2,3);
? ? tlist.emplace_back(t);
? ? tlist.emplace_back(2, 3, 4);? //直接調(diào)用了time的構(gòu)造函數(shù)在vector的內(nèi)存之中建立起新的對象? ? getchar();
}
執(zhí)行結(jié)果: copy?
?move (這次拷貝構(gòu)造函數(shù)的調(diào)用是因為vector本身的擴(kuò)容,也就是移動之前的已經(jīng)容納的time對象)
由上述代碼我們看到time對象可以直接利用emplace_back方法在vector上構(gòu)造對象,通過這樣的方式來減少不必要的內(nèi)存操作。(省去了拷貝構(gòu)造的環(huán)節(jié))。同樣的在main之中執(zhí)行下面的代碼邏輯:
int main(){ vector tlist;
? ? time t(1, 2, 3);
? ? tlist.emplace_back(move(t)); //調(diào)用move函數(shù)使time對象成為右值,可以利用移動構(gòu)造函數(shù)來拷貝對象
? ? tlist.emplace_back(2, 3, 4);? //直接調(diào)用了time的構(gòu)造函數(shù)在vector的內(nèi)存之中建立起新的對象
? ? getchar();
}
執(zhí)行結(jié)果:
? move? ? ? ? ? ? ? ? ?
? move (這次拷貝構(gòu)造函數(shù)的調(diào)用是因為vector本身的擴(kuò)容,也就是移動之前的已經(jīng)容納的time對象)
通過這樣的方式也減少不必要的內(nèi)存操作。(省去了移動構(gòu)造的環(huán)節(jié))。所以這就是為什么在C++11之后提倡大家使用emplace_back來代替舊代碼之中的push_back函數(shù)。如下面的代碼所示,在push_back底層也是調(diào)用了emplace_back來實現(xiàn)對應(yīng)的操作流程:
void push_back(const _Ty& _Val) {?
? ? ? emplace_back(_Val);
}
void push_back(_Ty&& _Val) {? ?
? ? ? emplace_back(_STD move(_Val));
}
2.emplace_back的實現(xiàn)源碼面前,了無秘密,接下來跟隨筆者直接來看看emplace_back的源代碼,來引出我們今天的主題:public: template decltype(auto) emplace_back(_Valty&&... _Val)
? ? ? ? {? // insert by perfectly forwarding into element at end, provide strong guarantee
? ? ? ? if (_Has_unused_capacity())
? ? ? ? ? ? {
? ? ? ? ? ? _Emplace_back_with_unused_capacity(_STD forward<_Valty>(_Val)...);
? ? ? ? ? ? }
? ? ? ? else
? ? ? ? ? ? {? // reallocate
? ? ? ? ? ? const size_type _Oldsize = size();
? ? ? ? ? ? if (_Oldsize == max_size())
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? _Xlength();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? const size_type _Newsize = _Oldsize + 1;
? ? ? ? ? ? const size_type _Newcapacity = _Calculate_growth(_Newsize);
? ? ? ? ? ? bool _Emplaced = false;
? ? ? ? ? ? const pointer _Newvec = this->_Getal().allocate(_Newcapacity);
? ? ? ? ? ? _Alty& _Al = this->_Getal();
? ? ? ? ? ? _TRY_BEGIN
? ? ? ? ? ? _Alty_traits::construct(_Al, _Unfancy(_Newvec + _Oldsize), _STD forward<_Valty>(_Val)...);
? ? ? ? ? ? _Emplaced = true;
? ? ? ? ? ? _Umove_if_noexcept(this->_Myfirst(), this->_Mylast(), _Newvec);
? ? ? ? ? ? _CATCH_ALL
? ? ? ? ? ? if (_Emplaced)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? _Alty_traits::destroy(_Al, _Unfancy(_Newvec + _Oldsize));
? ? ? ? ? ? ? ? }
? ? ? ? ? ? _Al.deallocate(_Newvec, _Newcapacity);
? ? ? ? ? ? _RERAISE;
? ? ? ? ? ? _CATCH_END
? ? ? ? ? ? _Change_array(_Newvec, _Newsize, _Newcapacity);
? ? ? ? ? ? }
#if _HAS_CXX17
? ? ? ? return (this->_Mylast()[-1]);
#endif /* _HAS_CXX17 */
? ? ? ? }
通過上述代碼可以看到,emplace_back的流程邏輯很簡單。先檢查vector的容量,不夠的話就擴(kuò)容,之后便通過**_Alty_traits::construct來創(chuàng)建對象。而最終利用強(qiáng)制類似裝換的指針來指向容器類之中對應(yīng)類的構(gòu)造函數(shù),并且利用可變長模板**將構(gòu)造函數(shù)所需要的內(nèi)容傳遞過去構(gòu)造新的對象。templatestatic void construct(_Alloc&, _Objty * const _Ptr, _Types&&... _Args) { // construct _Objty(_Types...) at _Ptr ::new (const_cast(static_cast(_Ptr)))
? ? ? ? ? ? _Objty(_STD forward<_Types>(_Args)...);
? ? ? ? }
emplace_back這里最為巧妙的部分就是利用可變長模板實現(xiàn)了,任意傳參的對象構(gòu)造。可變長模板是C++11新引進(jìn)的特性,接下來我們來詳細(xì)看看可變長模板是如何來使用,來實現(xiàn)任意長度的參數(shù)呢?
3.可變長模板與函數(shù)式編程首先,我們先看看,可變長模板的定義: template void f(T... args);
通過template來聲明參數(shù)包args,這個參數(shù)包中可以包含0到任意個參數(shù),并且作為函數(shù)參數(shù)調(diào)用。之后我們便可以在函數(shù)之中將參數(shù)包展開成一個一個獨立的參數(shù)。
假設(shè)我們有如下需求,需要定義一個max_num函數(shù)來求出一組任意參數(shù)數(shù)字的最大值,在C++11之前的版本或許需要這樣去定義這個函數(shù),也就是說我們需要一個參數(shù)來指定對應(yīng)參數(shù)的個數(shù),并且這個過程之中存在參數(shù)的類型不一致的潛在風(fēng)險,并不能在編譯期進(jìn)行反饋(不能在編譯期進(jìn)行對于動態(tài)語言來說根本不是什么大不了的問題,囧rz):
通過不斷遞歸的方式,提取可變長模板參數(shù)之中的首個元素,并且設(shè)置遞歸的終止點的方式來依次處理各個元素。這種處理函數(shù)的方式本質(zhì)上就是在通過遞歸的方式處理列表,這種編程思路在函數(shù)式編程語言之中十分常見,在C++之中看到這樣的用法,也讓筆者作為C++的入門選手感到很新奇。筆者曾經(jīng)接觸過Scala與Erlang語言之中大量利用了這種寫法,但是多層遞歸導(dǎo)致的必然是棧調(diào)用的開銷變大,利用尾遞歸的方式來優(yōu)化這樣的寫法,才能減少非必要的函數(shù)調(diào)用開銷。
4.小結(jié)
由emplace_back引申出來不少對C++11新特性的探索,筆者也僅僅做一些拋磚引玉的工作。作為程序員,希望大家能夠堅持不斷動態(tài)更新對語言的學(xué)習(xí)與探索來凝練與高效率的Coding,這也是筆者堅持更新該系列文章的初衷。
喜歡小編的希望多多關(guān)注我,久伴帶你了解C++風(fēng)景。