深入C基礎(chǔ)new運算符_第1頁
深入C基礎(chǔ)new運算符_第2頁
深入C基礎(chǔ)new運算符_第3頁
深入C基礎(chǔ)new運算符_第4頁
深入C基礎(chǔ)new運算符_第5頁
已閱讀5頁,還剩9頁未讀 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)

文檔簡介

1、深入c+基礎(chǔ)new運算符深入c+的newnew是c+的一個關(guān)鍵字,同時也是操作符關(guān)于new的話題非常多,因為它確實比較復(fù) 雜,也非常神秘,下面我將把我了解到的new有關(guān)的內(nèi)容做一個總結(jié)new的過程當(dāng)我們使用關(guān)鍵字new在堆上動態(tài)創(chuàng)建-個對象時,它實際上做了三件事:獲得一塊內(nèi) 存空間調(diào)用構(gòu)造函數(shù)返回止確的指針當(dāng)然,如果我們創(chuàng)建的是簡單類型的變量,那么第二 步會被省略假如我們定義了如下一個類a:class aiint i;public:a(int _i) : i (_i*_i) void say() printf("i=%dn", i); ;/調(diào)用new:a* pa 二 new

2、 a(3);那么上述動態(tài)創(chuàng)建一個對象的過程大致相當(dāng)于以下三句話(只是大致上):雖然從效 果上看,這三句話也得到了一個有效的指向堆上的a對象的指針pa,但區(qū)別在于,當(dāng) malloc失敗吋,它不會調(diào)用分配內(nèi)存失敗處理程序new_handlor,而使用new的話會的因 此我們還是要盡可能的使川new,除非有一些特殊的需求new的三種形態(tài)至!i目前為止,本文所提到的new都是指的new operator或稱為new expression,但事 實上在c+中一提到new,至少川能代表以下三種含義:new operatoroperator newplacement newnew operator就是我們平

3、時所使用的new,其行為就是前而所說的三個步驟,我們不能 更改它但具體到某一步驟中的行為,如果它不滿足我們的具體要求時,我們是有對能更改 它的三個步驟中最后一步只是簡單-的做一個指針的類型轉(zhuǎn)換,沒什么可說的,并且在編譯 出的代碼屮也并不需要這種轉(zhuǎn)換,只是人為的認(rèn)識罷了但前兩步就有些內(nèi)容了 new operator的第一步分配內(nèi)存實際上是通過調(diào)用operator new來完成的,這里的new實際 上是像加減乘除一樣的操作符,因此也是可以重載的operator new默認(rèn)情況下首先調(diào)用 分配內(nèi)存的代碼,嘗試得到一段堆上的空間,如果成功就返回,如果失敗,則轉(zhuǎn)而去調(diào)用 一個new_hander,然后繼

4、續(xù)重復(fù)前面過程如果我們對這個過程不滿意,就可以重載 operator new,來設(shè)置我們希望的行為例如:class apublic:void* operator new(size_t size)!printf("operator new callednz/) ;return :operator new(size);;a* a = new a ();這里通過::operator new調(diào)用了原有的全局的new,實現(xiàn)了在分配內(nèi)存z前輸出一句話 全局的operator new也是可以重載的,但這樣一來就不能再遞歸的使用new來分配內(nèi) 存,而只能使用malloc 了:void* operat

5、or new(size_t size)iprintf (''global newn");return malloc(size);;相應(yīng)的,delete也有delete operator和operator delete z分,后者也是可以重載的 并且,如果重載了 operator new,就應(yīng)該也相應(yīng)的重載operator delete,這是良好的編 程習(xí)慣now的第三種形態(tài)placement new是用來實現(xiàn)定位構(gòu)造的,因此對以實現(xiàn)nowoperator三步操作中的第二步,也就是在取得了一塊對以容納指定類型對象的內(nèi)存后, 在這塊內(nèi)存上構(gòu)造一個對彖,這有點類似于前面代碼

6、屮的p->a:a(3);這句話,但這并不 是一個標(biāo)準(zhǔn)的寫法,正確的寫法是使用placement new:#include <new. h>void main ()char ssizeof(a);a* p = (a*)s;new(p) a(3) ; /p->a:a(3);p->say ();對頭文件或<new. h>的引用是必須的,這樣才口j以使川placement new這里new(p) a(3)這種奇怪的寫法便是placement nev 了,它實現(xiàn)了在指定內(nèi)存地址上用指定類型的構(gòu) 造函數(shù)來構(gòu)造一個對象的功能,后面a(3)就是対構(gòu)造函數(shù)的顯式調(diào)用這里

7、不難發(fā)現(xiàn),這塊 指定的地址既可以是棧,又可以是堆,placement對此不加區(qū)分但是,除非特別必要,不 要直接使用placement now ,這畢竟不足用來構(gòu)造對象的正式寫法,只不過是new operator的一個步驟而已使用new operator地編譯器會口動牛:成對placement new的調(diào) 用的代碼,因此也會相應(yīng)的生成使用delete時調(diào)用析構(gòu)函數(shù)的代碼如果是像上而那樣在 棧上使用了 placement new,則必須手工調(diào)用析構(gòu)函數(shù),這也是顯式調(diào)用析構(gòu)函數(shù)的唯一 情況:p->a();當(dāng)我們覺得默認(rèn)的new operator對內(nèi)存的管理不能滿足我們的需要,而希望自己手工 的

8、管理內(nèi)存時,placement new就有用了 stl中的allocator就使用了這種方式,借助 placement new來實現(xiàn)更靈活有效的內(nèi)存管理處理內(nèi)存分配異常正如前而所說,operator new的默認(rèn)行為是請求分配內(nèi)存,如果成功則返回此內(nèi)存地 址,如果失敗則調(diào)用一個new handler,然后再重復(fù)此過程于是,想要從operator new的 執(zhí)行過程中返冋,則必然需要滿足下列條件z:于是,我們可以假設(shè)默認(rèn)情況下operator now的行為是這樣的:void* operator new(size_t size)void* p = nullwhile (!(p = malloc (

9、size)if (null = new_handler)throw bad_alloc();trynew_handler ();catch(bad alloc e)!throw e;cat ch()return p;在默認(rèn)情況下,new_handler的行為是拋出一個bad.alloc異常,因此上述循環(huán)只會執(zhí) 行一次但如果我們不希望使用默認(rèn)行為,可以口定義一個new_handler,并使用std: :set_new_handler兩數(shù)使其生效在自定義的new_handler中,我們可以拋出異常, 町以結(jié)束程序,也町以運行一些代碼使得有可能有內(nèi)存被空閑出來,從而下一次分配時也 許會成功,也可以通

10、過set_new_handler來安裝另一個可能史有效的new handler例如: void mynewhandler()!printf(new handler called!n);throw std:bad alloc();std:set_new_handler(mynewiiandler);這里new_handler程序在拋出異常z前會輸出一句話應(yīng)該注意,在new_handler的代碼 里應(yīng)該注意避免再嵌套冇new的調(diào)用,因為如果這里調(diào)用new再失敗的話,可能會再導(dǎo) 致對new_handler的調(diào)用,從而導(dǎo)致無限遞歸調(diào)用這是我猜的,并沒有嘗試過在編程吋我 們應(yīng)該注總到對new的調(diào)用是有可

11、能有異常被拋出的,因此在new的代碼周圍應(yīng)該注意保 持具事務(wù)性,即不能因為調(diào)川new失敗拋出異常來導(dǎo)致不正確的程序邏輯或數(shù)據(jù)結(jié)構(gòu)的出 現(xiàn)例如:class someclassstatic int count;someclass () public:static someclass* getnewinstance()count+;retum new someclass ();;靜態(tài)變量count用于記錄此類型生成的實例的個數(shù),在上述代碼中,如果因new分配內(nèi) 存失敗而拋岀異常,那么具實例個數(shù)并沒有增加,但count變量的值卻已經(jīng)多了一個,從 而數(shù)據(jù)結(jié)構(gòu)被破壞正確的寫法是:static somecl

12、ass* getnew instance()someclass* p 二 new someclass();count+;return p;這樣一來,如果new失敗則直接拋出異常,count的值不會增加類似的,在處理線程同 步時,也要注意類似的問題:void somefunc()lock(somemutex) ; /加一個鎖delete p;p = new someclass();unlock (somemutex);stl的內(nèi)存分配與traits技巧在stl原碼剖析一書中詳細(xì)分析了 sgi stl的內(nèi)存分配器的行為與直接使川new operator不同的是,sgi stl并不依賴c+默認(rèn)的內(nèi)存

13、分配方式,而是使用一套自行實現(xiàn) 的方案首先sgi stl將可用內(nèi)存整塊的分配,使z成為當(dāng)前進程可用的內(nèi)存,當(dāng)程序中確 實需要分配內(nèi)存時,先從這些已請求好的人內(nèi)存塊中嘗試取得內(nèi)存,如果失敗的話再嘗試 整塊的分配大內(nèi)存這種做法有效的避免了大量內(nèi)存碎片的出現(xiàn),提高了內(nèi)存管理效率為了實現(xiàn)這種方式,stl使川了 placement new,通過在自己管理的內(nèi)存空間上使用 placement new來構(gòu)造對象,以達到原冇new operator所具冇的功能template <class tl, class t2>inline void construct (tl* p, const t2&am

14、p; value)此函數(shù)接收-個u構(gòu)造的對象,通過拷貝構(gòu)造的方式在給定的內(nèi)存地址p上構(gòu)造一個新 對象,代碼中后半截t1 (value)便是placement new語法中調(diào)用構(gòu)造函數(shù)的寫法,如果傳 入的對象value止是所要求的類型t1,那么這里就相當(dāng)于調(diào)用拷貝構(gòu)造函數(shù)類似的,兇使 用了 placement new,編譯器不會自動產(chǎn)生調(diào)用析構(gòu)函數(shù)的代碼,需要手工的實現(xiàn): template <class t>inline void destory(t* pointer)與此同時,stl中還有一個接收兩個迭代器的destory版本,可將某容器上指定范圍內(nèi) 的對象全部銷毀典型的實現(xiàn)方式就

15、是通過一個循環(huán)來對此范i韋i內(nèi)的對象逐一調(diào)用析構(gòu)函數(shù) 如果所傳入的對象是非簡單類型,這樣做是必要的,但如果傳入的是簡單類型,或者根本 沒有必要調(diào)用析構(gòu)函數(shù)的口定義類型(例如只包含數(shù)個int成員的結(jié)構(gòu)體),那么再逐一 調(diào)用析構(gòu)函數(shù)是沒有必要的,也浪費了時間為此,stl使用了一種稱為type traits的技 巧,在編譯器就判斷出所傳入的類型是否需要調(diào)用析構(gòu)函數(shù):template <class forward!terator>inline void destory(forwarditerator first, forwarditerator last)_destory (first,

16、last, value_type(first);其中value_type()用于取出迭代器所指向的對彖的類型信息,于是:template<class forwarditerator, class t>inline void _destory(forwarditerator first, forwarditerator last, t*)typedef typename _type_traits<t>:has_trivial_destructor trivial_destructor; destory_aux(first, last, trivial destructor

17、();/如果需要調(diào)用析構(gòu)函數(shù):template<class forwardlterator>in1ine void _destory aux (forwardlterator first, forwarditerator last, _false_type) for(; first < last; +first)destory(&*first) ; /因first是迭代器,*first取出其真正內(nèi)容,然后再用&収地址/如果不需要,就什么也不做:tempaite<class forwardlterator>inline void _destory_a

18、ux(forwarditerator first, forwarditerator last, true_type) 因上述函數(shù)全都是inline的,所以多層的函數(shù)調(diào)用并不會對性能造成影響,最終編譯 的結(jié)杲根據(jù)具體的類型就只是一個for循環(huán)或者什么都沒有這里的關(guān)鍵在于_type_traits<t>這個模板類上,它根據(jù)不同的t類型左義岀不同的 has_trivial_destructor的結(jié)果,如果t是簡單-類型,就定義為true_type類型,否則 就定義為false_type類型其屮true_typefalse_type只不過是兩個沒有任何內(nèi)容的 類,對程序的執(zhí)行結(jié)果沒有什么意義

19、,但在編譯器看棗它對模板如何特化就具有非常重要 的指導(dǎo)意義了,正如上而代碼所示的那樣_type_traits<t>也是特化了的一系列模板類:struct _true_ type ;struct _false_type );tempi ate <cl ass t>struct _type_traitspublic:typedef _false _type has_trivial_destructor;templateo /模板特化struct _type_traits<int> /int 的特化版本public:typedef _true_type has_t

20、rivial_destructor;/具他簡單類型的特化版木如果要把一個自定義的類型myclass也定義為不調(diào)川析構(gòu)函數(shù),只需要相應(yīng)的定義 type_traits<t>的一個特化版本即可:templateostruct _type_traits<myclass>public:typedef _true_type has_trivial_destructor;;模板是比較高級的c+編程技巧,模板特化模板偏特化就更是技巧性很強的東西,stl 中的type_traits充分借助模板特化的功能,實現(xiàn)了在程序編譯期通過編譯器來決定為每 一處調(diào)用後用哪個特化版本,于是在不增加編程復(fù)

21、雜性的前提下大大提高了程序的運行效 率更詳細(xì)的內(nèi)容可參考stl源碼剖析第二三章中的相關(guān)內(nèi)容帶有的new和delete我們經(jīng)常會通過new來動態(tài)創(chuàng)建一個數(shù)組,例如:嚴(yán)格的說,上述代碼是不正確的,因為我們在分配內(nèi)存時使用的是new,而并不是簡 單的new,但帑放內(nèi)存時卻用的是delete正確的寫法是使用deleted :deleted s;但是,上述錯誤的代碼似乎也能編譯執(zhí)行,并不會帶來什么錯謀事實上,new與 ne譏jdelete -與delete是冇區(qū)別的,特別是當(dāng)用來操作復(fù)雜類型時假如針対一個我們口 定義的類myclass使用new:myclass* p 二 new myclass10;上述

22、代碼的結(jié)杲是在堆上分配了 10個連續(xù)的myclass實例,并且已經(jīng)対它們依次調(diào)用 了構(gòu)造函數(shù),于是我們得到了 10個可用的對象,這一點與javac#有區(qū)別的,javac#中這 樣的結(jié)果只是得到了 10個null換句話說,使用這種寫法時myclass必須擁有不帶參數(shù)的 構(gòu)造函數(shù),否則會發(fā)現(xiàn)編譯期錯誤,因為編譯器無法調(diào)用有參數(shù)的構(gòu)造函數(shù)當(dāng)這樣構(gòu)造成功后,我們可以再將其釋放,釋放時使川delete】:delete p;當(dāng)我們對動態(tài)分配的數(shù)組調(diào)川delete時,其行為根據(jù)所屮請的變量類型會有所不同如 果p指向簡單類型,如intchar等,其結(jié)果只不過是這塊內(nèi)存被回收,此時使用delete 與delet

23、e沒有區(qū)別,但如果p指向的是復(fù)雜類型,deleted會針對動態(tài)分配得到的每個 對象調(diào)用析構(gòu)函數(shù),然后再釋放內(nèi)存因此,如果我們對上述分配得到的p指針直接使用 delete來回收,雖然編譯期不報什么錯誤(因為編譯器根本看不岀來這個指針p是如何分 配的),但在運行時(debug情況下)會給出一個debug assertio n failed提示到這里,我們很容易提出一個問題delete是如何知道要為多少個對象調(diào)用析構(gòu)函數(shù) 的?要冋答這個問題,我們可以首先看一看ne譏的重載class myclassiint a;public:myclass() printf("ctorn"); &

24、quot;myclass () printf (z,dtorn,z) ; ;void* operator new(size_t size)!void* p 二 operator new(size);printf ("calling new with size=%d address=%pn,z, size, p);return p;/主函數(shù)myclass* me = new myclass3;printf (''address of mc=%pnz/, me);deleted me;運行此段代碼,得到的結(jié)果為:(vc2005)ctorctorctordtordtordt

25、or雖然對構(gòu)造函數(shù)和析構(gòu)函數(shù)的調(diào)用結(jié)果都在預(yù)料之中,但所中請的內(nèi)存空間大小以及地 址的數(shù)值卻出現(xiàn)了問題我們的類myclass的大小顯然是4個字節(jié),并冃申請的數(shù)組中有3 個元素,那么應(yīng)該一共申請12個字節(jié)才對,但事實上系統(tǒng)卻為我們申請了 16字節(jié),并且 在operator new返后我們得到的內(nèi)存地址是實際川請得到的內(nèi)存地址值加4的結(jié)果也就 是說,當(dāng)為復(fù)朵類型動態(tài)分配數(shù)組吋,系統(tǒng)自動在最終得到的內(nèi)存地址前空出了4個字 節(jié),我們冇理由相信這4個字節(jié)的內(nèi)容-與動態(tài)分配數(shù)組的長度冇關(guān)通過單步跟蹤,很容易 發(fā)現(xiàn)這4個字節(jié)對應(yīng)的int值為0x00000003,也就是說記錄的是我們分配的對象的個數(shù)改 變一下

26、分配的個數(shù)然后再次觀察的結(jié)果證實了我的想法于是,我們也有理由認(rèn)為ne譏 operator的行為和當(dāng)于下面的偽代碼:templato <class t>t* new(int count)int size = sizeof(t) * count + 4; void* p = t:operator ncw (size); *(int*)p = count;t* pt = (t*)(int)p + 4);for(int i 二 0; i count; i+) new(&pti) t();return pt;上述示意性的代碼省略了杲常處理的部分,只是展示當(dāng)我們對一個復(fù)雜類型使用new

27、 來動態(tài)分配數(shù)組時其真正的行為是什么,從中可以看到它分配了比預(yù)期多4個字節(jié)的內(nèi)存 并用它來保存對象的個數(shù),然后對于后而每一塊空間使用placement new來調(diào)用無參構(gòu)造函數(shù),這也就解釋了為什么這種情況下類必須有無參構(gòu)造函數(shù),最后再將首地址返冋類似 的,我們很容易寫出相應(yīng)的delete的實現(xiàn)代碼:template <class t>void delete(t* pt)!int count = (int*)pt)t;for(int i = 0; i count; i+)pt i t ();void* p = (void*) (int)pt 4);t:operator delete(

28、p);由此可見,在默認(rèn)情況下operator new與operator new的行為是相同的,operator delete operator delete 也是,不同的是 new operator new operatordelete operator delote operator當(dāng)然,我們可以根據(jù)不同的需耍來選擇重載帶冇和不帶冇 的operator new和delete,以滿足不同的具體需求把前面類myclass的代碼稍做修改注釋掉析構(gòu)函數(shù),然后再來看看程序的輸hh calling new with size=12 address=003a5a58ctorctorctoraddress

29、of mc=003a5a58這一次,new老老實實的申請了 12個字節(jié)的內(nèi)存,并且申請的結(jié)果與new operator 返回的結(jié)果也是相同的,看來,是否在前面添加4個字節(jié),只取決于這個類有沒有析構(gòu)函 數(shù),當(dāng)然,這么說并不確切,正確的說法是這個類是否需要調(diào)用構(gòu)造函數(shù),因為如下兩種 情況下雖然這個類沒聲明析構(gòu)函數(shù),但還是多巾請了4個字節(jié):一是這個類中擁冇需耍調(diào) 用析構(gòu)函數(shù)的成員,二是這個類繼承自需要調(diào)用析構(gòu)函數(shù)的類于是,我們町以遞歸的定義 需要調(diào)用析構(gòu)函數(shù)的類為以下三種情況之一:1顯式的聲明了析構(gòu)函數(shù)的2擁有需要調(diào)川析構(gòu)函數(shù)的類的成員的3繼承自需要調(diào)用析構(gòu)函數(shù)的類的類似的,動態(tài)審請簡單類型的數(shù)組時

30、,也不會多申請4個字節(jié)于是在這兩種情況下,w 放內(nèi)存時使m delete或delete都可以,但為養(yǎng)成良好的習(xí)慣,我們還是應(yīng)該注意只要 是動態(tài)分配的數(shù)組,禪放時就使用delete:釋放內(nèi)存吋如何知道長度但這同時又帶來了新問題,既然巾請無需調(diào)用析構(gòu)函數(shù)的類或簡單類型的數(shù)組時并沒冇 記錄個數(shù)信息,那么operator delete,或更直接的說free()是如何來回收這塊內(nèi)存的 呢?這就要研究malloco返冋的內(nèi)存的結(jié)構(gòu)了與ne譏類似的是,實際上在ma hoc ()申請 內(nèi)存時也多申請了數(shù)個字節(jié)的內(nèi)容,只不過這與所審請的變量的類型沒有任何關(guān)系,我們 從調(diào)用malloc時所傳入的參數(shù)也對以理解這一點它只接收了要申請的內(nèi)存的長度,并不 關(guān)系這塊內(nèi)存用來保存什么類型下面運行這樣一段代碼做個實驗:char *p = 0;for(int i = 0; i < 40; i += 4)char* s = new chari;printf (,zalloc %2d bytes, address=%p distance=%dn", i, s,

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論