C++11的版本在vector容器添加了emplace_back方法,相對(duì)于原先的push_back方法能夠在一定程度上提升vector容器的表現(xiàn)性能。所以我們從STL源碼角度來(lái)切入,看看這兩種方法有什么樣的區(qū)別,新引進(jìn)的方法又有什么可學(xué)習(xí)參考之處。

1.emplace_back的用法emplace_back方法最大的改進(jìn)就在與可以利用類(lèi)本身的構(gòu)造函數(shù)直接在內(nèi)存之中構(gòu)建對(duì)象,而不需要調(diào)用類(lèi)的拷貝構(gòu)造函數(shù)與移動(dòng)構(gòu)造函數(shù)。舉個(gè)栗子,假設(shè)如下定義了一個(gè)時(shí)間類(lèi)time,該類(lèi)同時(shí)定義了拷貝構(gòu)造函數(shù)與移動(dòng)構(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)存之中建立起新的對(duì)象? ? getchar();
}
執(zhí)行結(jié)果: copy?
?move (這次拷貝構(gòu)造函數(shù)的調(diào)用是因?yàn)関ector本身的擴(kuò)容,也就是移動(dòng)之前的已經(jīng)容納的time對(duì)象)
由上述代碼我們看到time對(duì)象可以直接利用emplace_back方法在vector上構(gòu)造對(duì)象,通過(guò)這樣的方式來(lái)減少不必要的內(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對(duì)象成為右值,可以利用移動(dòng)構(gòu)造函數(shù)來(lái)拷貝對(duì)象
? ? tlist.emplace_back(2, 3, 4);? //直接調(diào)用了time的構(gòu)造函數(shù)在vector的內(nèi)存之中建立起新的對(duì)象
? ? getchar();
}
執(zhí)行結(jié)果:
? move? ? ? ? ? ? ? ? ?
? move (這次拷貝構(gòu)造函數(shù)的調(diào)用是因?yàn)関ector本身的擴(kuò)容,也就是移動(dòng)之前的已經(jīng)容納的time對(duì)象)
通過(guò)這樣的方式也減少不必要的內(nèi)存操作。(省去了移動(dòng)構(gòu)造的環(huán)節(jié))。所以這就是為什么在C++11之后提倡大家使用emplace_back來(lái)代替舊代碼之中的push_back函數(shù)。如下面的代碼所示,在push_back底層也是調(diào)用了emplace_back來(lái)實(shí)現(xiàn)對(duì)應(yīng)的操作流程:
void push_back(const _Ty& _Val) {?
? ? ? emplace_back(_Val);
}
void push_back(_Ty&& _Val) {? ?
? ? ? emplace_back(_STD move(_Val));
}
2.emplace_back的實(shí)現(xiàn)源碼面前,了無(wú)秘密,接下來(lái)跟隨筆者直接來(lái)看看emplace_back的源代碼,來(lái)引出我們今天的主題: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 */
? ? ? ? }
通過(guò)上述代碼可以看到,emplace_back的流程邏輯很簡(jiǎn)單。先檢查vector的容量,不夠的話(huà)就擴(kuò)容,之后便通過(guò)**_Alty_traits::construct來(lái)創(chuàng)建對(duì)象。而最終利用強(qiáng)制類(lèi)似裝換的指針來(lái)指向容器類(lèi)之中對(duì)應(yīng)類(lèi)的構(gòu)造函數(shù),并且利用可變長(zhǎng)模板**將構(gòu)造函數(shù)所需要的內(nèi)容傳遞過(guò)去構(gòu)造新的對(duì)象。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這里最為巧妙的部分就是利用可變長(zhǎng)模板實(shí)現(xiàn)了,任意傳參的對(duì)象構(gòu)造??勺冮L(zhǎng)模板是C++11新引進(jìn)的特性,接下來(lái)我們來(lái)詳細(xì)看看可變長(zhǎng)模板是如何來(lái)使用,來(lái)實(shí)現(xiàn)任意長(zhǎng)度的參數(shù)呢?
3.可變長(zhǎng)模板與函數(shù)式編程首先,我們先看看,可變長(zhǎng)模板的定義: template void f(T... args);
通過(guò)template來(lái)聲明參數(shù)包args,這個(gè)參數(shù)包中可以包含0到任意個(gè)參數(shù),并且作為函數(shù)參數(shù)調(diào)用。之后我們便可以在函數(shù)之中將參數(shù)包展開(kāi)成一個(gè)一個(gè)獨(dú)立的參數(shù)。
假設(shè)我們有如下需求,需要定義一個(gè)max_num函數(shù)來(lái)求出一組任意參數(shù)數(shù)字的最大值,在C++11之前的版本或許需要這樣去定義這個(gè)函數(shù),也就是說(shuō)我們需要一個(gè)參數(shù)來(lái)指定對(duì)應(yīng)參數(shù)的個(gè)數(shù),并且這個(gè)過(guò)程之中存在參數(shù)的類(lèi)型不一致的潛在風(fēng)險(xiǎn),并不能在編譯期進(jìn)行反饋(不能在編譯期進(jìn)行對(duì)于動(dòng)態(tài)語(yǔ)言來(lái)說(shuō)根本不是什么大不了的問(wèn)題,囧rz):
通過(guò)不斷遞歸的方式,提取可變長(zhǎng)模板參數(shù)之中的首個(gè)元素,并且設(shè)置遞歸的終止點(diǎn)的方式來(lái)依次處理各個(gè)元素。這種處理函數(shù)的方式本質(zhì)上就是在通過(guò)遞歸的方式處理列表,這種編程思路在函數(shù)式編程語(yǔ)言之中十分常見(jiàn),在C++之中看到這樣的用法,也讓筆者作為C++的入門(mén)選手感到很新奇。筆者曾經(jīng)接觸過(guò)Scala與Erlang語(yǔ)言之中大量利用了這種寫(xiě)法,但是多層遞歸導(dǎo)致的必然是棧調(diào)用的開(kāi)銷(xiāo)變大,利用尾遞歸的方式來(lái)優(yōu)化這樣的寫(xiě)法,才能減少非必要的函數(shù)調(diào)用開(kāi)銷(xiāo)。
4.小結(jié)
由emplace_back引申出來(lái)不少對(duì)C++11新特性的探索,筆者也僅僅做一些拋磚引玉的工作。作為程序員,希望大家能夠堅(jiān)持不斷動(dòng)態(tài)更新對(duì)語(yǔ)言的學(xué)習(xí)與探索來(lái)凝練與高效率的Coding,這也是筆者堅(jiān)持更新該系列文章的初衷。
喜歡小編的希望多多關(guān)注我,久伴帶你了解C++風(fēng)景。