C++霧中風(fēng)景9:emplace_back與可變長(zhǎng)模板

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)景。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容