下載本文檔
版權說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權,請進行舉報或認領
文檔簡介
C++中的變長參數(shù)函數(shù)。這MemNew新參與的項目中,為了使用共享內(nèi)存和自定義內(nèi)存池,我們自己定義了MemNew函數(shù),且在函數(shù)內(nèi)部對于非pod類型自動執(zhí)行構造函數(shù)。在需要的地方調(diào)用自定義的函數(shù)。這MemNew樣就帶來一個問題,使用stl的類都有默認構造函數(shù),以及復制構造函數(shù)等。但使用共享內(nèi)存和內(nèi)存池的類可能沒有默認構造函數(shù),而是定義了多個參數(shù)的構造函數(shù),于是如何將參數(shù)傳入函數(shù)便成了問題。1.變長參數(shù)函數(shù)首先回顧一下較多使用的變長參數(shù)函數(shù),最經(jīng)典的便是printfoexternintprintf(constchar*format,...);以上是一個變長參數(shù)的函數(shù)聲明。我們自己定義一個測試函數(shù):復制代碼#include<stdarg.h>#include<stdio.h>inttestparams(intcount,...){va_listargs;va_start(args,count);for(inti=0;i<count;++i){intarg=va_arg(args,int);printf("arg%d=%d",i,arg);}va_end(args);return0;}intmain(){testparams(3,10,11,12);return0;}復制代碼變長參數(shù)函數(shù)的解析,使用到三個宏va_start,va_arg和va_end,再看va_list的定義typedefchar*va_list;只是一個char指針。這幾個宏如何解析傳入的參數(shù)呢?函數(shù)的調(diào)用,是一個壓棧,保存,跳轉的過程。簡單的流程描述如下:把參數(shù)從右到左依次壓入棧;調(diào)用call指令,把下一條要執(zhí)行的指令的地址作為返回地址入棧;(被調(diào)用函數(shù)執(zhí)行完后會回到該地址繼續(xù)執(zhí)行)當前的ebp(基址指針)入棧保存,然后把當前esp(棧頂指針)賦給ebp作為新函數(shù)棧幀的基址;執(zhí)行被調(diào)用函數(shù),局部變量等人棧;返回值放入eax,leave,ebp賦給esp,esp所存的地址賦給ebp;(這里可能需要拷貝臨時返回對象)從返回地址開始繼續(xù)執(zhí)行;(把返回地址所存的地址給eip)由于開始的時候從右至左把參數(shù)壓棧,va_start傳入最左側的參數(shù),往右的參數(shù)依次更早被壓入棧,因此地址依次遞增(棧頂?shù)刂纷钚?。va_arg傳入當前需要獲得的參數(shù)的類型,便可以利用sizeof計算偏移量,依次獲取后面的參數(shù)值。復制代碼#define_INTSIZEOF(n)((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))#define_ADDRESSOF(v)(&const_cast<char&>(reinterpret_cast<constvolatilechar&>(v)))#define_crt_va_start_a(ap,v)((void)(ap=(va_list)_ADDRESSOF(v)+_INTSIZEOF(v)))#define_crt_va_arg(ap,t)(*(t*)((ap+=_INTSIZEOF(t))-_INTSIZEOF(t)))#define_crt_va_end(ap)((void)(ap=(va_list)0))#define_crt_va_start(ap,x)((void)(vcrtvastartverifyargumenttype<decltype(x)>(),_crt_va_start_a(ap,x)))#defineva_start_crt_va_start#defineva_arg_crt_va_arg#defineva_end_crt_va_end復制代碼上述宏定義中,_INTSIZEOF(n)將地址的低2位指令,做內(nèi)存的4字節(jié)對齊。每次取參數(shù)時,調(diào)用_crt_va_arg(ap,t),返回t類型參數(shù)地址的值,同時將ap偏移到t之后。最后,調(diào)用_crt_va_end(ap)將ap置0.變長參數(shù)的函數(shù)的使用及其原理看了宏定義是很好理解的。從上文可知,要使用變長參數(shù)函數(shù)的參數(shù),我們必須知道傳入的每個參數(shù)的類型。printf中,有format字符串中的特殊字符組合來解析后面的參數(shù)類型。但是當傳入類的構造函數(shù)的參數(shù)時,我們并不知道每個參數(shù)都是什么類型,雖然參數(shù)能夠依次傳入函數(shù),但無法解析并獲取每個參數(shù)的數(shù)值。因此傳統(tǒng)的變長參數(shù)函數(shù)并不足以解決傳入任意構造函數(shù)參數(shù)的問題。2.變長參數(shù)模板我們需要用到C++11的新特性,變長參數(shù)模板。這里舉一個使用自定義內(nèi)存池的例子。定義一個內(nèi)存池類MemPool.h,以count個類型T為單元分配內(nèi)存,默認分配一個對象。每當內(nèi)存內(nèi)空閑內(nèi)存不夠,則一次申請MEMPOOL_NEW_SIZE個內(nèi)存對象。內(nèi)存池本身只負責內(nèi)存分配,不做初始化工作,因此不需要傳入任何參數(shù),只需實例化模板分配相應類型的內(nèi)存即可。復制代碼#ifndefUTIL_MEMPOOL_H#defineUTIL_MEMPOOL_H#include<stdlib.h>#defineMEMPOOL_NEW_SIZE8template<typenameT,size_tcount=1>classMemPool11private:121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556unionMemObj{char_obj[1];MemObj*_freelink;};public:staticvoid*Allocate。{if(!_freelist){refill();}MemObj*alloc_mem=_freelist;_freelist=_freelist->_freelink;++_size;return(void*)alloc_mem;}staticvoidDeAllocate(void*p){MemObj*q=(MemObj*)p;q->_freelink=_freelist;_freelist=q;--_size;}staticsize_tTotalSize(){return_totalsize;}staticsize_tSize(){return_size;}private:staticvoidrefill(){size_tsize=sizeof(T)*count;char*new_mem=(char*)malloc(size*MEMPOOL_NEW_SIZE);for(inti=0;i<MEMPOOL_NEW_SIZE;++i){MemObj*free_mem=(MemObj*)(new_mem+i*size);free_mem->_freelink=_freelist;_freelist=free_mem;}.totalsize+=MEMPOOL_NEW_SIZE;staticMemObj*_freelist;staticsize_t_totalsize;staticsize_t_size;};6162template<typenameT,size_tcount>63typenameMemPool<T,count>::MemObj*MemPool<T,count>::_freelist=NULL;6465template<typenameT,size_tcount>66size_tMemPool<T,count>::_totalsize=0;6768template<typenameT,size_tcount>69size_tMPool<T,count>::_size=0;70#endif復制代碼接下來在沒有變長參數(shù)的情況下,實現(xiàn)通用MemNew和MemDelete函數(shù)模板。這里不對函數(shù)模板作詳細解釋,用函數(shù)模板我們可以對不同的類型實現(xiàn)同樣的內(nèi)存池分配操作。如下:復制代碼template<classT>T*MemNew(size_tcount){T*p=(T*)MemPool<T,count>::Allocate();if(p!=NULL)TOC\o"1-5"\h\z{if(!std::is_pod<T>::value){for(size_ti=0;i<count;++i){new(&p[i])TO;}}}returnp;}template<classT>T*MemDelete(T*p,size_tcount){if(p!=NULL){if(!std::is_pod<T>::value){for(size_ti=0;i<count;++i)27p[i].~T();27placementnew將報錯。對于placementnew將報錯。對于pod類型,可以省去調(diào)用構造函std::forward<Args>實現(xiàn)參數(shù)的完美轉發(fā)。這樣,無論傳入的類2829)30MemPool<T,count>::DeAllocate(p);31)32)復制代碼上述實現(xiàn)中,使用placementnew對申請的內(nèi)存進行構造,使用了默認構造函數(shù),當申請內(nèi)存的類型不具備默認構造函數(shù)時,數(shù)的過程。引入C++11變長模板參數(shù)后MemNew修改為如下復制代碼template<classT,class...Args>T*MemNew(size_tcount,Args&&…args){T*p=(T*)MemPool<T,count>::Allocate。;if(p!=NULL)TOC\o"1-5"\h\z{if(!std::is_pod<T>::value){for(size_ti=0;i<count;++i){new(&p[i])T(std::forward<Args>(args)...);)))returnp;)復制代碼以上函數(shù)定義包含了多個特性,后面我將一一解釋,其中class...Args表示變長參數(shù)模板,函數(shù)參數(shù)中Args&&為右值引用。型具有什么樣的構造函數(shù),都能夠完美執(zhí)行placementnew。C++11中引入了變長參數(shù)模板的概念,來解決參數(shù)個數(shù)不確定的模板。復制代碼template<class…T>classTest{};Test<>test0;Test<int>test1;Test<int,int>test2;Test<int,int,long>test3;template<class...T>voidtest(T...args);test();test<int>(0);10test<int,int,long>(0,0,0L);復制代碼以上分別是使用變長參數(shù)類模板和變長參數(shù)函數(shù)模板的例子。2.1變長參數(shù)函數(shù)模板T...args為形參包,其中args是模式,形參包中可以有0到任意多個參數(shù)。調(diào)用函數(shù)時,可以傳任意多個實參。對于函數(shù)定義來說,該如何使用參數(shù)包呢?在上文的MemNew中,我們使用std::forward依次將參數(shù)包傳入構造函數(shù),并不關注每個參數(shù)具體是什么。如果需要,我們可以用sizeof...(args)操作獲取參數(shù)個數(shù),也可以把參數(shù)包展開,對每個參數(shù)做更多的事。展開的方法有兩種,遞歸函數(shù),逗號表達式。遞歸函數(shù)方式展開,模板推導的時候,一層層遞歸展開,最后到?jīng)]有參數(shù)時用定義的一般函數(shù)終止。復制代碼1voidtest。2{3}4template<classT,class...Args>voidtest(Tfirst,Args...args)7{std::cout<<typeid(T).name0<<""<<first<<std::endl;test(args...);}11test<int,int,long>(0,0,0L);output:int0int0long0復制代碼逗號表達式方式展開,利用數(shù)組的參數(shù)初始化列表和逗號表達式,逐一執(zhí)行print每個參數(shù)。復制代碼template<classT>voidprint(Targ)3{std::cout<<typeid(T).name()<<""<<arg<<std::endl;}6template<class...Args>voidtest(Args...args)9{intarr[]={(print(args),0)...};}13test(0,0,0L);14output:int0int0long0復制代碼2變長參數(shù)類模板變長參數(shù)類模板,一般情況下可以方便我們做一些編譯期計算??梢酝ㄟ^偏特化和遞歸推導的方式依次展開模板參數(shù)。復制代碼template<classT,class...Types>classTest{public:enum{value=Test<T>::value+Test<Types...>::value,};};template<classT>classTest<T>{public:enum{value=sizeof(T),};};Test<int,int,long>test;std::cout<<test.value;output:12復制代碼2.3右值引用和完美轉發(fā)對于變長參數(shù)函數(shù)模板,需要將形參包展開逐個處理的需求不多,更多的還是像本文的MemNew這樣的需求,最終整個傳入某個現(xiàn)有的函數(shù)。我們把重點放在參數(shù)的傳遞上。要理解右值引用,需要先說清楚左值和右值。左值是內(nèi)存中有確定存儲地址的對象的表達式的值;右值則是非左值的表達式的值。const左值不可被賦值,臨時對象的右值可以被賦值。左值與右值的根本區(qū)別在于是否能用&運算符獲得內(nèi)存地址。復制代碼inti=0;//i左值int*p=&i;//i左值int&foo();foo()=42;//foo()左值int*pl=&foo();//foo()左值intfoo1();intj=0;j=foo1();//foo右值intk=j+1;//j+1右值int*p2=&foo1();//錯誤,無法取右值的地址j=1;//1右值復制代碼理解左值和右值之后,再來看引用,對左值的引用就是左值引用,對右值(純右值和臨終值)的引用就是右值引用。如下函數(shù)foo,傳入int類型,返回int類型,這里傳入函數(shù)的參數(shù)0和返回值0都是右值印能用&取得地址)。于是,未做優(yōu)化的情況下,傳入?yún)?shù)0的時候,我們需要把右值0拷貝給param,函數(shù)返回的時候需要將0拷貝給臨時對象,臨時對象再拷貝給res。當然現(xiàn)在的編譯器都做了返回值優(yōu)化,返回對象是直接創(chuàng)建在返回后的左值上的,這里只用來舉個例子復制代碼intfoo(intparam){printf("%d",param);return0;}intres=foo(0);復制代碼顯然,這里的拷貝都是多余的。可能我們會想要優(yōu)化,首先將參數(shù)int改為int&,傳入左值引用,于是0無法傳入了,當然我們可以改成constint&,這樣終于省去了傳參的拷貝。intfoo(constint¶m){printf("%d",param);return0;}由于constint&既可以是左值也可以是右值,傳入0或者int變量都能夠滿足。(但是似乎既然有左值引用的int&類型,就應該有對應的傳入右值引用的類型int&&)。另外,這里返回的右值0,似乎不通過拷貝就無法賦值給左值res。于是有了移動語義,把臨時對象的內(nèi)容直接移動給被賦值的左值對象(std::move)。和右值引用,X&&是到數(shù)據(jù)類型X的右值引用。復制代碼intresult=0;int&&foo(int&¶m)printf("%d",param);returnstd::move(result);)int&&res=foo(0);int*pres=&res;復制代碼將foo改為右值引用參數(shù)和返回值,返回右值引用,免去拷貝。這里res是具名引用,運算符右側的右值引用作為左值,可以取地址。右值引用既有左值性質(zhì),也有右值性質(zhì)。上述例子還只存在于拷貝的性能問題。回到MemNew這樣的函數(shù)模板。復制代碼template<classT>T*Test(Targ){returnnewT(arg);)template<classT>T*Test(T&arg){returnnewT(arg);)template<classT>T*Test(constT&arg){returnnewT(arg);)template<classT>T*Test(T&&arg){returnnewT(std::forward<T>(arg));)復制代碼上述的前三種方式傳參,第一種首先有拷貝消耗,其次有的參數(shù)就是需要修改的左值。第二種方式則無法傳常數(shù)等右值。第三種方式雖然左值右值都能傳,卻無法對傳入的參數(shù)進行修改。第四種方式使用右值引用,可以解決參數(shù)完美轉發(fā)的問題。std::forward能夠根據(jù)實參的數(shù)據(jù)類型,返回相應類型的左值和右值引用,將參數(shù)完整不動的傳遞下去。解釋這個原理涉及到引用塌縮規(guī)則T&&->T&T&&&->T&T&&&&->T&&復制代碼template<classT>structremove_reference{typedefTtype;};template<classT>structremove_reference<T&>{typedefTtype;};template<classT>structremove_reference<T&&>{typedefTtype;};template<classT>T&&forward(typenamestd::remove_reference<T>::type&t){returnstatic_cast<T&&>(t);}template<classT>typenamestd::remove_reference<T>::type&&move(T&&a)noexcept{returnstatic_cast<typenamestd::remove_referen
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2023雙方汽車租賃協(xié)議書七篇
- 色素性癢疹病因介紹
- 臂叢神經(jīng)損傷病因介紹
- 個體防護用品基礎知識
- 《模具設計與制造李集仁》課件-第6章
- (2024)清潔汽油項目可行性研究報告寫作范本(一)
- 2024-2025年遼寧省錦州市第十二中學第三次月考英語問卷-A4
- 天津市五區(qū)縣重點校聯(lián)考2022-2023學年高二下學期期中考試語文試卷
- 電氣施工對土建工程的 要求與配合- 電氣施工技術98課件講解
- 2023年監(jiān)護病房項目籌資方案
- 山東青島幼兒師范高等專科學校招聘考試試題及答案
- 【川教版】《生命 生態(tài) 安全》五上第8課《防患于未“燃”》課件
- 卓有成效的管理者pdf
- 職務侵占罪預防
- 《芣苢》 統(tǒng)編版高中語文必修上冊
- 幼兒數(shù)學核心經(jīng)驗通用課件
- 2024年英語必修第二冊 Unit2 全單元教學設計
- 代理做賬創(chuàng)業(yè)計劃書
- 2023-2024學年人教部編統(tǒng)編版八年級上冊歷史期末檢測卷(含答案解析)
- 設備維修和維護保養(yǎng)基礎知識培訓
- 智能制造項目供應鏈管理戰(zhàn)略方案
評論
0/150
提交評論