原則27_1:如何在堆中建立對(duì)象?
即,除了new以外用其他方法創(chuàng)建對(duì)象都不行。
其實(shí)這只需要把構(gòu)造函數(shù)和析構(gòu)函數(shù)中的至少一個(gè)設(shè)成private即可。如果構(gòu)造函數(shù)是private而析構(gòu)函數(shù)是非private的,那么你無(wú)法在外界正常常見對(duì)象。如果構(gòu)造函數(shù)是public而析構(gòu)函數(shù)是private的就會(huì)造成產(chǎn)生的對(duì)象無(wú)法被析構(gòu),這時(shí)C++判定此對(duì)象不合法。如果構(gòu)造函數(shù)和析構(gòu)函數(shù)都是private那就不必再說(shuō)了。
所以書中給出的建議是你只需要private析構(gòu)函數(shù)即可,然后你再寫一個(gè)偽析構(gòu)函數(shù)來(lái)調(diào)用真析構(gòu)函數(shù)完成析構(gòu)工作。
那為什么選擇private析構(gòu)函數(shù)呢?因?yàn)槊總€(gè)類的析構(gòu)函數(shù)只有一個(gè),這樣方便,而類的構(gòu)造函數(shù)有很多。
不過(guò)因?yàn)閜rivate析構(gòu)函數(shù)的關(guān)系一個(gè)對(duì)象無(wú)法正常建立,這也導(dǎo)致類不能用于父類和復(fù)合。
到此,你可以通過(guò)類的實(shí)例的指針來(lái)使用類了。
此時(shí),我突然想到指針相當(dāng)于零存零取,而聲明對(duì)象相當(dāng)于整存爭(zhēng)取。對(duì)象要求完整性,而指針則不。
原則27_2:如何解決不能繼承和不能復(fù)合的問(wèn)題?
繼承從訪問(wèn)權(quán)限的角度講是從protected和public為前提的,因此如果你想讓類被繼承,那它的構(gòu)造函數(shù)和析構(gòu)函數(shù)都不能是private的。但是你又想讓類的對(duì)象只建立在堆上所以你只好選擇把析構(gòu)函數(shù)設(shè)成protected的。因?yàn)檫@樣的話外界仍然不能如常地建立一個(gè)合法的對(duì)象。
而復(fù)合是說(shuō)你把類的對(duì)象放到別的類中充當(dāng)成員。因?yàn)樵瓌t27_2中談?wù)摰木褪峭ㄟ^(guò)new建立對(duì)象的方法,這說(shuō)明類的對(duì)象的指針可以如常使用,那么你只需要讓該類的指針充當(dāng)其他類的成員即可。
原則27_3:采用異常+NEW來(lái)判斷對(duì)象是否在堆上會(huì)失敗
還是上面的例子,假如A是一個(gè)只能產(chǎn)生對(duì)對(duì)象的類,現(xiàn)在A的指針被作為B的成員,但是現(xiàn)在B沒有被要求建立對(duì)對(duì)象。這就說(shuō)明B能夠正常建立對(duì)象。
那么問(wèn)題來(lái)了。B對(duì)象中A的成分是建立在堆上?還是在棧上?
答案是不知道。
書中提出了幾種解決方案。
解決方案一:因?yàn)槿绻麑?duì)象在堆上建立,那肯定是需要調(diào)用new操作符,你可以自定義一個(gè)new調(diào)用真正的new,并設(shè)置一個(gè)標(biāo)志位如果new操作符代碼得以執(zhí)行,該標(biāo)志位就會(huì)被置位,顯然標(biāo)志位在自定義new操作符代碼中。并且,該標(biāo)志位在構(gòu)造函數(shù)中還原。按照常理來(lái)說(shuō),你聲明一個(gè)對(duì)象,就得new一次,調(diào)用構(gòu)造函數(shù)一次。然后,你再寫一個(gè)嵌套異常類,一旦檢測(cè)到標(biāo)志位為非堆,就拋出該異常。
但是這不適用于new[],即生產(chǎn)數(shù)組堆對(duì)象,因?yàn)閚ew[]只執(zhí)行了一次而構(gòu)造函數(shù)需要執(zhí)行n次。當(dāng)你第2次執(zhí)行構(gòu)造函數(shù)的時(shí)候,你的程序會(huì)因?yàn)闃?biāo)志位為非堆而產(chǎn)生異常,這樣就達(dá)不到判斷的目的。
原則27_4:使用置位來(lái)判斷對(duì)象是否在堆中
書中還提供了一種方法,就是用new來(lái)建立一個(gè)對(duì)象,那么這個(gè)對(duì)象肯定在堆上。然后用另一個(gè)new出來(lái)的對(duì)象給這個(gè)new對(duì)象初始化。這有點(diǎn)像拆了

東墻補(bǔ)西墻。
因?yàn)槔ㄌ?hào)中的對(duì)象沒有用delete刪除所以它被留在堆中造成了內(nèi)存泄露。作者的希望原本是new一次構(gòu)造一次,在new的時(shí)候置位一次,構(gòu)造的時(shí)候再置位一次,這樣連續(xù)兩次,每次都可以通過(guò)判斷位來(lái)推斷對(duì)象是否在堆中。但是實(shí)際上編譯器可能的順序是new、new、構(gòu)造、構(gòu)造,這樣第二次new的置位被第三次構(gòu)造清除,第四次構(gòu)造的時(shí)候判斷出該對(duì)象不在堆中,即便它實(shí)際上在堆中。所以,這也是一種失敗的解決方案。
它太依賴于編譯器的獨(dú)特性,不具有共性,因此不具有可移植性。
原則27_5:用地址空間比較來(lái)判斷對(duì)象是否在堆中
因?yàn)樵诤芏嘞到y(tǒng)中,棧在高地址空間,堆在低地址空間,這樣的話你可以通過(guò)判斷地址大小關(guān)系來(lái)推斷對(duì)象是不是在堆中。但是這也行不通,因?yàn)樵诤芏嘞到y(tǒng)中靜態(tài)存儲(chǔ)區(qū)和堆挨著而且比堆的地址空間還低,所以你通過(guò)地址比較這種方法并不能準(zhǔn)確地判斷出對(duì)象在不在堆上,也許它還能在靜態(tài)存儲(chǔ)區(qū)上。
所以這種方法也不可取。
原則27_5:沒有方法能判斷對(duì)象在堆上
很遺憾的是根本沒有任何方法能判斷一個(gè)對(duì)象是否在堆上,更準(zhǔn)確地說(shuō)是沒有一種通用的,可移植的辦法能判斷對(duì)象是否在堆上。它必須完全依賴于特定的系統(tǒng)。
不是所有堆中的對(duì)象都能使用delete刪除的。比如說(shuō),你聲明了一個(gè)類A的指針,顯然這個(gè)指針在堆上。但是,類A包含了類B的對(duì)象成員,這時(shí)如果你想delete類A的對(duì)象就會(huì)不安全,因?yàn)轭怋對(duì)象不在堆上。
書中說(shuō)你可以把研究的對(duì)象從堆上的對(duì)象轉(zhuǎn)移到一段地址空間上來(lái)。即,你只要清空分配給對(duì)對(duì)象的地址空間即可,這是可行的。因?yàn)榈刂房臻g的起址是知道的,清空和判斷是否安全的方法也是可以寫出來(lái)的,所以但是處理堆上的空間,這很容易。
不過(guò)這種方法有3個(gè)限制因素。
1、自定義版本的operator new和operator delete極易與其他軟件的operator new和operator delete產(chǎn)生不兼容,從而影響可移植性;
2、這樣做并非必要,但仍需開銷;
3、用來(lái)驗(yàn)證指針是否在堆中的方法無(wú)法奏效。因?yàn)榛诙嗬^承和虛擬繼承的類對(duì)象的地址不唯一,所以不能保證給定的地址是否在new所分配的地址空間中。
書中設(shè)計(jì)了一種能夠判斷分配、釋放和判斷安全與否的類,因?yàn)檫@是一種能夠被普遍使用的類,所以它被設(shè)計(jì)成抽象類。它把由new分配的地址統(tǒng)統(tǒng)裝進(jìn)了一個(gè)static的list中,delete的時(shí)候也從這個(gè)list中刪除。
當(dāng)你應(yīng)用dynamic_cast對(duì)指針進(jìn)行轉(zhuǎn)換時(shí)返回的指針是這塊內(nèi)存的起始處,它只能被應(yīng)用到具有至少一個(gè)virtual函數(shù)的對(duì)象的指針上??梢园裠ynamic_cast應(yīng)用到上面類的判斷函數(shù)實(shí)現(xiàn)上。
這個(gè)類一旦設(shè)計(jì)完成就可以被應(yīng)用到許多類上,用來(lái)跟蹤指針,具體方法就是繼承這個(gè)類。
這方法不適合于內(nèi)置類型,只適合自定義類型。
原則27_6:禁止對(duì)象被直接實(shí)例化情況下建立堆對(duì)象
因?yàn)槎褜?duì)象的建立總是需要new操作符,那么在類內(nèi)部把new操作符設(shè)置為private即可,你就不能直接使用new了。
因?yàn)閚ew可重用所以把operator new設(shè)成static,另外,一般new的用法是單獨(dú)使用,這也決定你應(yīng)該使用static修飾。
與之對(duì)應(yīng),你應(yīng)該把operator delete也設(shè)成static private。從語(yǔ)法上來(lái)講你可以不把delete設(shè)成private的,但是,因?yàn)閚ew和delete歷來(lái)都是搭配使用的,new什么樣delete就什么樣,所以一旦你不給與delete和new一樣的待遇,總是讓人感覺有些不舒服。
原則27_7:禁止對(duì)象作為子類的父類而在堆中被建立
在這種情況下,子類會(huì)繼承父類的private new和delete,所以子類也不能建立堆上的對(duì)象。
這時(shí)你只需要在子類中把new和delete重新聲明為public即可,它就會(huì)把父類的new和delete重寫。我還擔(dān)心重寫new和delete會(huì)和重寫普通函數(shù)有區(qū)別,但實(shí)際上一樣。
這樣整個(gè)繼承體系用的都是子類的new和delete,父類的new和delete不會(huì)被使用。那么父類的對(duì)象也會(huì)在堆上被建立,所以在這種情況下無(wú)法阻止父類對(duì)象在堆上被建立,必須想別的辦法來(lái)對(duì)付這種情況。

如下圖所示:


原則27_8:禁止被嵌入的對(duì)象在堆中建立
在這種情況下只需要管理嵌入對(duì)象,只要把嵌入對(duì)象的new和delete聲明為private即可,被嵌入的類無(wú)需做任何處理。被嵌入的類的對(duì)象在堆上被建立時(shí)并不會(huì)導(dǎo)致嵌入類對(duì)象在堆上被建立。
因?yàn)榍度腩悓?duì)象通常是直接在被嵌入類中創(chuàng)建出來(lái)的,它本身就不是在堆中,而是在棧里面被建立的(這是我的推測(cè))。
多謝捧場(chǎng)
如果您覺得我的文章有價(jià)值,那么賞臉打賞一個(gè),鄙人感激不盡。不過(guò),不打賞看看也是好的,如果有不對(duì)的地方,還請(qǐng)您多多指正。