本原則是針對(duì)在多線程環(huán)境下內(nèi)存中動(dòng)態(tài)存儲(chǔ)區(qū)也就是堆中空間資源的訪問而產(chǎn)生的田弥,因?yàn)槎褜儆谌中再Y源,每個(gè)線程都試圖去使用它侧巨,又因?yàn)槊總€(gè)線程是互斥地使用資源惨远,那自然就會(huì)存在一個(gè)先到先得的問題,這就是所謂的競(jìng)速孵奶。你要知道堆空間也是有限的疲酌,于是就可能出現(xiàn)當(dāng)某個(gè)線程是要使用堆空間時(shí),堆卻沒有足夠的空間提供給這個(gè)線程使用的時(shí)候了袁。這個(gè)時(shí)候你再使用new來(lái)分配空間就會(huì)失敗朗恳。雖然C++中提供了默認(rèn)的處理過程,但是有的時(shí)候你就是希望自定義整個(gè)new的過程载绿,那該如何是好粥诫?本原則就是來(lái)幫你解決這個(gè)問題的。
在這里你要知道C++是如何處理動(dòng)態(tài)內(nèi)存分配不足的問題的崭庸。以前如果遇到這種情況new會(huì)直接返回一個(gè)null怀浆,現(xiàn)在一般是返回一個(gè)bad_alloc異常。C++處理new異常通常有一個(gè)指向錯(cuò)誤處理函數(shù)的函數(shù)指針new-handler怕享,它負(fù)責(zé)對(duì)new分配不成功的處理执赡。還有一個(gè)使用這個(gè)函數(shù)指針進(jìn)行異常邏輯處理的函數(shù)叫set_new_handler,它們都在new頭文件里面函筋。
有趣的是這個(gè)set_new_handler是以new-handler為參數(shù)并以new-handler為返回值的沙合,那又是怎么回事呢?作為參數(shù)的這個(gè)new-handler是指new分配錯(cuò)誤時(shí)被調(diào)用的錯(cuò)誤處理函數(shù)驻呐,注意灌诅!這個(gè)處理函數(shù)不一定非要是系統(tǒng)的庫(kù)函數(shù),它也可以是用戶自定義的含末,不過因?yàn)閚ew-handler是一個(gè)無(wú)參數(shù)無(wú)返回值的函數(shù)的指針猜拾,所以這個(gè)錯(cuò)誤處理函數(shù)也必須是這樣的一個(gè)函數(shù)。而那個(gè)返回的new-handler指向的是set_new_handler被調(diào)用前正在被執(zhí)行的new-handler類型函數(shù)佣盒。在這里我對(duì)我自己的理解表示不確定挎袜。
這樣只要new分配不成功它就不斷地接受新的new-handler返回舊的new-handler知道new能分配成功。這樣肥惭,設(shè)計(jì)一個(gè)良好的new-handler必須做到以下幾點(diǎn):
1盯仪、讓可用內(nèi)存更多。這一點(diǎn)首先要做到有足夠的可供分配的內(nèi)存空間蜜葱,其次就是每次調(diào)用new-handler的時(shí)候就要及時(shí)釋放掉那些沒有分配成功的內(nèi)存空間全景;
2、安裝另一個(gè)new-handler牵囤。用更強(qiáng)大的new-handler替換當(dāng)前的new-handler爸黄;
3滞伟、卸載new-handler。如果沒有可用的new-handler炕贵,就將參數(shù)new-handler置為null使異常拋出梆奈;
4、拋出bad_alloc異常称开;
5亩钟、直接abort或者exit結(jié)束,啥也不做鳖轰。
對(duì)于實(shí)現(xiàn)自定義new-handler和set_new_handler清酥,作者的思想是在自定義接口中去調(diào)用這些系統(tǒng)函數(shù)來(lái)完成自定義功能,具體來(lái)說你就是在類內(nèi)實(shí)現(xiàn)就好了脆霎。在此作者給了個(gè)例子:
從上圖可見总处,主要就是在類中自定義兩個(gè)公有的static接口,這樣每個(gè)類的實(shí)例都可以使用這倆接口睛蛛。其中一個(gè)是new的重載函數(shù)鹦马,另一個(gè)是set-new-handler。它還有一個(gè)私有的static的new-handler類型的指針忆肾,用來(lái)set-new-handler被調(diào)用前正在被使用的new-handler函數(shù)荸频。這個(gè)set-new-handler所做的事情就是先把當(dāng)前正在運(yùn)行的new-handler函數(shù)保存起來(lái),然后更改currentHandler的指向?yàn)樾碌膎ew-handler客冈,最后返回那個(gè)保存起來(lái)的new-handler旭从。
這個(gè)被重載的new主要完成以下工作:
Step 1、在發(fā)生錯(cuò)誤的時(shí)候調(diào)用標(biāo)準(zhǔn)的set-new-handler场仲,這會(huì)將set-new-handler的參數(shù)置為新的global-new-handler和悦,我想這個(gè)global-new-handler是系統(tǒng)提供的new-handler吧;
Step 2渠缕、global new會(huì)被調(diào)用鸽素,我想它也就是系統(tǒng)提供的那個(gè)new吧。如果new失敗了亦鳞,那么這個(gè)new會(huì)去調(diào)用類中的new-handler馍忽,因?yàn)橛蒘tep 1可知此時(shí)的new-handler實(shí)際上是系統(tǒng)的new-handler。如果這個(gè)系統(tǒng)的new-handler還是無(wú)法解決問題燕差,它只能拋出bad-alloc異常遭笋。不過在這之前,類中的new必須把原來(lái)的系統(tǒng)的new-handler作為類中的set-new-handler的返回值來(lái)恢復(fù)系統(tǒng)的new-handler徒探。為了先前的new-handler能得到恢復(fù)瓦呼,類必須把先前的new-handler作為資源來(lái)管理;
Step 3测暗、如果系統(tǒng)的new能分配成功央串,那么類中的new就應(yīng)該返回該內(nèi)存的指針谎替,析構(gòu)函數(shù)必須把先前的new-handler恢復(fù)回去。
因?yàn)檫@個(gè)方案并不因類的不同而不同蹋辅,所以可以考慮把它做成父類的模版,讓各個(gè)子類去繼承這個(gè)特性挫掏,于是在這里作者提到了奇異遞歸模版模式侦另,簡(jiǎn)稱CRTP(Curiously Recurring Template Pattern)。它的樣子大致如下尉共。
可以看出一個(gè)父類是以子類類型來(lái)進(jìn)行特化的褒傅。
文中還提到為了支持以前在舊規(guī)范時(shí)候?qū)懗鰜?lái)的代碼,C++標(biāo)準(zhǔn)委員會(huì)提供了另一種new它在失敗時(shí)直接返回null袄友,它被稱為nothrow形式的new殿托。其使用形式如下:
這種new不好,因?yàn)樗哂芯窒扌跃珧迹荒芄茏‘?dāng)前代碼后續(xù)代碼它就管不了了支竹,所以相比之下還是拋出異常的new比較好。
總結(jié):
1鸠按、作為set-new-handler函數(shù)中的參數(shù)的new-handler允許用戶自定義礼搁,它在內(nèi)存分配不足時(shí)被調(diào)用。
2目尖、Nothrow new太局限馒吴,它只適合于一段代碼,對(duì)于后續(xù)的構(gòu)造函數(shù)什么的拋出的異常卻束手無(wú)策瑟曲。