欧美三区_成人在线免费观看视频_欧美极品少妇xxxxⅹ免费视频_a级毛片免费播放_鲁一鲁中文字幕久久_亚洲一级特黄

C++內(nèi)存對(duì)象大會(huì)戰(zhàn)

系統(tǒng) 2800 0

C++ 內(nèi)存對(duì)象大會(huì)戰(zhàn)

如果一個(gè)人自稱為程序高手,卻對(duì)內(nèi)存一無(wú)所知,那么我可以告訴你,他一定在吹牛。用 C或C++寫(xiě)程序,需要更多地關(guān)注內(nèi)存,這不僅僅是因?yàn)閮?nèi)存的分配是否合理直接影響著程序的效率和性能,更為主要的是,當(dāng)我們操作內(nèi)存的時(shí)候一不小心就會(huì)出現(xiàn)問(wèn)題,而且很多時(shí)候,這些問(wèn)題都是不易發(fā)覺(jué)的,比如內(nèi)存泄漏,比如懸掛指針。筆者今天在這里并不是要討論如何避免這些問(wèn)題,而是想從另外一個(gè)角度來(lái)認(rèn)識(shí)C++內(nèi)存對(duì)象。

我們知道, C++ 將內(nèi)存劃分為三個(gè)邏輯區(qū)域:堆、棧和靜態(tài)存儲(chǔ)區(qū)。既然如此,我稱位于它們之中的對(duì)象分別為堆對(duì)象,棧對(duì)象以及靜態(tài)對(duì)象。那么這些不同的內(nèi)存對(duì)象有什么區(qū)別了?堆對(duì)象和棧對(duì)象各有什么優(yōu)劣了?如何禁止創(chuàng)建堆對(duì)象或棧對(duì)象了?這些便是今天的主題。

一.基本概念

先來(lái)看看棧。棧,一般用于存放局部變量或?qū)ο?,如我? 在函數(shù)定義中 用類似下面語(yǔ)句聲明的對(duì)象:

Type stack_object ;

stack_object 便是一個(gè)棧對(duì)象,它的生命期是從定義點(diǎn)開(kāi)始,當(dāng)所在函數(shù)返回時(shí),生命結(jié)束。

另外,幾乎所有的臨時(shí)對(duì)象都是棧對(duì)象。比如,下面的函數(shù)定義:

Type fun Type object ;

這個(gè)函數(shù)至少產(chǎn)生兩個(gè)臨時(shí)對(duì)象,首先,參數(shù)是按值傳遞的,所以會(huì)調(diào)用拷貝構(gòu)造函數(shù)生成一個(gè)臨時(shí)對(duì)象 object_copy1 ,在函數(shù)內(nèi)部使用的不是使用的不是 object ,而是 object_copy1 ,自然, object_copy1 是一個(gè)棧對(duì)象,它在函數(shù)返回時(shí)被釋放;還有這個(gè)函數(shù)是值返回的,在函數(shù)返回時(shí),如果我們不考慮返回值優(yōu)化( NRV ),那么也會(huì)產(chǎn)生一個(gè)臨時(shí)對(duì)象 object_copy2 ,這個(gè)臨時(shí)對(duì)象會(huì)在函數(shù)返回后一段時(shí)間內(nèi)被釋放。比如某個(gè)函數(shù)中有如下代碼:

Type tt ,result ; // 生成兩個(gè)棧對(duì)象

tt = fun tt ; // 函數(shù)返回時(shí),生成的是一個(gè)臨時(shí)對(duì)象 object_copy2

上面的第二個(gè)語(yǔ)句的執(zhí)行情況是這樣的,首先函數(shù) fun 返回時(shí)生成一個(gè)臨時(shí)對(duì)象 object_copy2 ,然后再調(diào)用賦值運(yùn)算符執(zhí)行

tt = object_copy2 ; // 調(diào)用賦值運(yùn)算符

看到了嗎?編譯器在我們毫無(wú)知覺(jué)的情況下,為我們生成了這么多臨時(shí)對(duì)象,而生成這些臨時(shí)對(duì)象的時(shí)間和空間的開(kāi)銷可能是很大的,所以,你也許明白了,為什么對(duì)于“大”對(duì)象最好用 const 引用傳遞代替按值進(jìn)行函數(shù)參數(shù)傳遞了。

接下來(lái),看看堆。堆,又叫自由存儲(chǔ)區(qū),它是在程序執(zhí)行的過(guò)程中動(dòng)態(tài)分配的,所以它最大的特性就是 動(dòng)態(tài)性 。在 C++ 中,所有堆對(duì)象的創(chuàng)建和銷毀都要由程序員負(fù)責(zé),所以,如果處理不好,就會(huì)發(fā)生內(nèi)存問(wèn)題。如果分配了堆對(duì)象,卻忘記了釋放,就會(huì)產(chǎn)生內(nèi)存泄漏;而如果已釋放了對(duì)象,卻沒(méi)有將相應(yīng)的指針置為 NULL ,該指針就是所謂的“懸掛指針”,再度使用此指針時(shí),就會(huì)出現(xiàn)非法訪問(wèn),嚴(yán)重時(shí)就導(dǎo)致程序崩潰。

那么, C++ 中是怎樣分配堆對(duì)象的?唯一的方法就是用 new (當(dāng)然,用類 malloc 指令也可獲得 C 式堆內(nèi)存),只要使用 new ,就會(huì)在堆中分配一塊內(nèi)存,并且返回指向該堆對(duì)象的指針。

再來(lái)看看靜態(tài)存儲(chǔ)區(qū)。所有的靜態(tài)對(duì)象、全局對(duì)象都于靜態(tài)存儲(chǔ)區(qū)分配。關(guān)于全局對(duì)象,是在 main() 函數(shù)執(zhí)行前就分配好了的。其實(shí),在 main() 函數(shù)中的顯示代碼執(zhí)行之前,會(huì)調(diào)用一個(gè)由編譯器生成的 _main() 函數(shù),而 _main() 函數(shù)會(huì)進(jìn)行所有全局對(duì)象的的構(gòu)造及初始化工作。而在 main() 函數(shù)結(jié)束之前,會(huì)調(diào)用由編譯器生成的 exit 函數(shù),來(lái)釋放所有的全局對(duì)象。比如下面的代碼:

void main void

{

… …// 顯式代碼

}

實(shí)際上,被轉(zhuǎn)化成這樣:

void main void

{

_main () ; // 隱式代碼,由編譯器產(chǎn)生,用以構(gòu)造所有全局對(duì)象

… … // 顯式代碼

… …

exit () ; // 隱式代碼,由編譯器產(chǎn)生,用以釋放所有全局對(duì)象

}

所以,知道了這個(gè)之后,便可以由此引出一些技巧,如,假設(shè)我們要在 main() 函數(shù)執(zhí)行之前做某些準(zhǔn)備工作,那么我們可以將這些準(zhǔn)備工作寫(xiě)到一個(gè)自定義的全局對(duì)象的構(gòu)造函數(shù)中,這樣,在 main() 函數(shù)的顯式代碼執(zhí)行之前,這個(gè)全局對(duì)象的構(gòu)造函數(shù)會(huì)被調(diào)用,執(zhí)行預(yù)期的動(dòng)作,這樣就達(dá)到了我們的目的。

剛才講的是靜態(tài)存儲(chǔ)區(qū)中的全局對(duì)象,那么,局部靜態(tài)對(duì)象了?局部靜態(tài)對(duì)象通常也是在函數(shù)中定義的,就像棧對(duì)象一樣,只不過(guò),其前面多了個(gè) static 關(guān)鍵字。局部靜態(tài)對(duì)象的生命期是從其所在函數(shù)第一次被調(diào)用,更確切地說(shuō),是當(dāng)?shù)谝淮螆?zhí)行到該靜態(tài)對(duì)象的聲明代碼時(shí),產(chǎn)生該靜態(tài)局部對(duì)象,直到整個(gè)程序結(jié)束時(shí),才銷毀該對(duì)象。

還有一種靜態(tài)對(duì)象,那就是它作為 class 的靜態(tài)成員??紤]這種情況時(shí),就牽涉了一些較復(fù)雜的問(wèn)題。

第一個(gè)問(wèn)題是 class 的靜態(tài)成員對(duì)象的生命期, class 的靜態(tài)成員對(duì)象隨著第一個(gè) class object 的產(chǎn)生而產(chǎn)生,在整個(gè)程序結(jié)束時(shí)消亡。也就是有這樣的情況存在,在程序中我們定義了一個(gè) class ,該類中有一個(gè)靜態(tài)對(duì)象作為成員,但是在程序執(zhí)行過(guò)程中,如果我們沒(méi)有創(chuàng)建任何一個(gè)該 class object ,那么也就不會(huì)產(chǎn)生該 class 所包含的那個(gè)靜態(tài)對(duì)象。還有,如果創(chuàng)建了多個(gè) class object ,那么所有這些 object 都共享那個(gè)靜態(tài)對(duì)象成員。

第二個(gè)問(wèn)題是,當(dāng)出現(xiàn)下列情況時(shí):

class Base

{

public:

static Type s_object ;

}

class Derived1 : public Base / / 公共繼承

{

… …// other data

}

class Derived2 : public Base / / 公共繼承

{

… …// other data

}

Base example ;

Derivde1 example1 ;

Derivde2 example2 ;

example.s_object = …… ;

example1.s_object = …… ;

example2.s_object = …… ;

請(qǐng)注意上面標(biāo)為黑體的三條語(yǔ)句,它們所訪問(wèn)的 s_object 是同一個(gè)對(duì)象嗎?答案是肯定的,它們的確是指向同一個(gè)對(duì)象,這聽(tīng)起來(lái)不像是真的,是嗎?但這是事實(shí),你可以自己寫(xiě)段簡(jiǎn)單的代碼驗(yàn)證一下。我要做的是來(lái)解釋為什么會(huì)這樣?

我們知道,當(dāng)一個(gè)類比如 Derived1 ,從另一個(gè)類比如 Base 繼承時(shí),那么,可以看作一個(gè) Derived1 對(duì)象中含有一個(gè) Base 型的對(duì)象,這就是一個(gè) subobject 。一個(gè) Derived1 對(duì)象的大致內(nèi)存布局如下:

<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 193.5pt; HEIGHT: 169.5pt" type="#_x0000_t75"><imagedata o:title="subobject" src="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image001.jpg"></imagedata></shape>

讓我們想想,當(dāng)我們將一個(gè) Derived1 型的對(duì)象傳給一個(gè)接受非引用 Base 型參數(shù)的函數(shù)時(shí)會(huì)發(fā)生切割,那么是怎么切割的呢?相信現(xiàn)在你已經(jīng)知道了,那就是僅僅取出了 Derived1 型的對(duì)象中的 subobject ,而忽略了所有 Derived1 自定義的其它數(shù)據(jù)成員,然后將這個(gè) subobject 傳遞給函數(shù)(實(shí)際上,函數(shù)中使用的是這個(gè) subobject 的拷貝)。

所有繼承 Base 類的派生類的對(duì)象都含有一個(gè) Base 型的 subobject (這是能用 Base 型指針指向一個(gè) Derived1 對(duì)象的關(guān)鍵所在,自然也是多態(tài)的關(guān)鍵了),而所有的 subobject 和所有 Base 型的對(duì)象都共用同一個(gè) s_object 對(duì)象,自然,從 Base 類派生的整個(gè)繼承體系中的類的實(shí)例都會(huì)共用同一個(gè) s_object 對(duì)象了。上面提到的 example 、 example1 、 example2 的對(duì)象布局如下圖所示:

<shape id="_x0000_i1026" style="WIDTH: 405pt; HEIGHT: 198.75pt" type="#_x0000_t75"><imagedata o:title="subobject_share" src="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image002.jpg"><font size="3"></font></imagedata></shape>

二.三種內(nèi)存對(duì)象的比較

棧對(duì)象的優(yōu)勢(shì)是在適當(dāng)?shù)臅r(shí)候自動(dòng)生成,又在適當(dāng)?shù)臅r(shí)候自動(dòng)銷毀,不需要程序員操心;而且棧對(duì)象的創(chuàng)建速度一般較堆對(duì)象快,因?yàn)榉峙涠褜?duì)象時(shí),會(huì)調(diào)用 operator new 操作, operator new 會(huì)采用某種內(nèi)存空間搜索算法,而該搜索過(guò)程可能是很費(fèi)時(shí)間的,產(chǎn)生棧對(duì)象則沒(méi)有這么麻煩,它僅僅需要移動(dòng)棧頂指針就可以了。但是要注意的是,通常棧空間容量比較小,一般是 1MB 2MB ,所以體積比較大的對(duì)象不適合在棧中分配。特別要注意遞歸函數(shù)中最好不要使用棧對(duì)象,因?yàn)殡S著遞歸調(diào)用深度的增加,所需的??臻g也會(huì)線性增加,當(dāng)所需??臻g不夠時(shí),便會(huì)導(dǎo)致棧溢出,這樣就會(huì)產(chǎn)生運(yùn)行時(shí)錯(cuò)誤。

堆對(duì)象,其產(chǎn)生時(shí)刻和銷毀時(shí)刻都要程序員精確定義,也就是說(shuō),程序員對(duì)堆對(duì)象的生命具有完全的控制權(quán)。我們常常需要這樣的對(duì)象,比如,我們需要?jiǎng)?chuàng)建一個(gè)對(duì)象,能夠被多個(gè)函數(shù)所訪問(wèn),但是又不想使其成為全局的,那么這個(gè)時(shí)候創(chuàng)建一個(gè)堆對(duì)象無(wú)疑是良好的選擇,然后在各個(gè)函數(shù)之間傳遞這個(gè)堆對(duì)象的指針,便可以實(shí)現(xiàn)對(duì)該對(duì)象的共享。另外,相比于??臻g,堆的容量要大得多。實(shí)際上,當(dāng)物理內(nèi)存不夠時(shí),如果這時(shí)還需要生成新的堆對(duì)象,通常不會(huì)產(chǎn)生運(yùn)行時(shí)錯(cuò)誤,而是系統(tǒng)會(huì)使用虛擬內(nèi)存來(lái)擴(kuò)展實(shí)際的物理內(nèi)存。

接下來(lái)看看 static 對(duì)象。

首先是全局對(duì)象。全局對(duì)象為類間通信和函數(shù)間通信提供了一種最簡(jiǎn)單的方式,雖然這種方式并不優(yōu)雅。一般而言,在完全的面向?qū)ο笳Z(yǔ)言中,是不存在全局對(duì)象的,比如 C# ,因?yàn)槿謱?duì)象意味著不安全和高耦合,在程序中過(guò)多地使用全局對(duì)象將大大降低程序的健壯性、穩(wěn)定性、可維護(hù)性和可復(fù)用性。 C++ 也完全可以剔除全局對(duì)象,但是最終沒(méi)有,我想原因之一是為了兼容 C 。

其次是類的靜態(tài)成員,上面已經(jīng)提到,基類及其派生類的所有對(duì)象都共享這個(gè)靜態(tài)成員對(duì)象,所以當(dāng)需要在這些 class 之間或這些 class objects 之間進(jìn)行數(shù)據(jù)共享或通信時(shí),這樣的靜態(tài)成員無(wú)疑是很好的選擇。

接著是靜態(tài)局部對(duì)象,主要可用于保存該對(duì)象所在函數(shù)被屢次調(diào)用期間的中間狀態(tài),其中一個(gè)最顯著的例子就是遞歸函數(shù),我們都知道遞歸函數(shù)是自己調(diào)用自己的函數(shù),如果在遞歸函數(shù)中定義一個(gè) nonstatic 局部對(duì)象,那么當(dāng)遞歸次數(shù)相當(dāng)大時(shí),所產(chǎn)生的開(kāi)銷也是巨大的。這是因?yàn)? nonstatic 局部對(duì)象是棧對(duì)象,每遞歸調(diào)用一次,就會(huì)產(chǎn)生一個(gè)這樣的對(duì)象,每返回一次,就會(huì)釋放這個(gè)對(duì)象,而且,這樣的對(duì)象只局限于當(dāng)前調(diào)用層,對(duì)于更深入的嵌套層和更淺露的外層,都是不可見(jiàn)的。每個(gè)層都有自己的局部對(duì)象和參數(shù)。

在遞歸函數(shù)設(shè)計(jì)中,可以使用 static 對(duì)象替代 nonstatic 局部對(duì)象(即棧對(duì)象),這不僅可以減少每次遞歸調(diào)用和返回時(shí)產(chǎn)生和釋放 nonstatic 對(duì)象的開(kāi)銷,而且 static 對(duì)象還可以保存遞歸調(diào)用的中間狀態(tài),并且可為各個(gè)調(diào)用層所訪問(wèn)。

三.使用棧對(duì)象的意外收獲

前面已經(jīng)介紹到,棧對(duì)象是在適當(dāng)?shù)臅r(shí)候創(chuàng)建,然后在適當(dāng)?shù)臅r(shí)候自動(dòng)釋放的,也就是棧對(duì)象有自動(dòng)管理功能。那么棧對(duì)象會(huì)在什么會(huì)自動(dòng)釋放了?第一,在其生命期結(jié)束的時(shí)候;第二,在其所在的函數(shù)發(fā)生異常的時(shí)候。你也許說(shuō),這些都很正常啊,沒(méi)什么大不了的。是的,沒(méi)什么大不了的。但是只要我們?cè)偕钊胍稽c(diǎn)點(diǎn),也許就有意外的收獲了。

棧對(duì)象,自動(dòng)釋放時(shí),會(huì)調(diào)用它自己的析構(gòu)函數(shù)。如果我們?cè)跅?duì)象中封裝資源,而且在棧對(duì)象的析構(gòu)函數(shù)中執(zhí)行釋放資源的動(dòng)作,那么就會(huì)使資源泄漏的概率大大降低,因?yàn)? 棧對(duì)象可以自動(dòng)的釋放資源,即使在所在函數(shù)發(fā)生異常的時(shí)候 。實(shí)際的過(guò)程是這樣的:函數(shù)拋出異常時(shí),會(huì)發(fā)生所謂的 stack_unwinding (堆?;貪L),即堆棧會(huì)展開(kāi),由于是棧對(duì)象,自然存在于棧中,所以在堆?;貪L的過(guò)程中,棧對(duì)象的析構(gòu)函數(shù)會(huì)被執(zhí)行,從而釋放其所封裝的資源。除非,除非在析構(gòu)函數(shù)執(zhí)行的過(guò)程中再次拋出異常――而這種可能性是很小的,所以用棧對(duì)象封裝資源是比較安全的。基于此認(rèn)識(shí),我們就可以創(chuàng)建一個(gè)自己的句柄或代理來(lái)封裝資源了。智能指針( auto_ptr )中就使用了這種技術(shù)。在有這種需要的時(shí)候,我們就希望我們的資源封裝類只能在棧中創(chuàng)建,也就是要限制在堆中創(chuàng)建該資源封裝類的實(shí)例。

四.禁止產(chǎn)生堆對(duì)象

上面已經(jīng)提到,你決定禁止產(chǎn)生某種類型的堆對(duì)象,這時(shí)你可以自己創(chuàng)建一個(gè)資源封裝類,該類對(duì)象只能在棧中產(chǎn)生,這樣就能在異常的情況下自動(dòng)釋放封裝的資源。

那么怎樣禁止產(chǎn)生堆對(duì)象了?我們已經(jīng)知道,產(chǎn)生堆對(duì)象的唯一方法是使用 new 操作,如果我們禁止使用 new 不就行了么。再進(jìn)一步, new 操作執(zhí)行時(shí)會(huì)調(diào)用 operator new ,而 operator new 是可以重載的。方法有了,就是使 new operator private ,為了對(duì)稱,最好將 operator delete 也重載為 private ?,F(xiàn)在,你也許又有疑問(wèn)了 , 難道創(chuàng)建棧對(duì)象不需要調(diào)用 new 嗎?是的,不需要,因?yàn)閯?chuàng)建棧對(duì)象不需要搜索內(nèi)存,而是直接調(diào)整堆棧指針,將對(duì)象壓棧,而 operator new 的主要任務(wù)是搜索合適的堆內(nèi)存,為堆對(duì)象分配空間,這在上面已經(jīng)提到過(guò)了。好,讓我們看看下面的示例代碼:

#include <stdlib.h> // 需要用到 C 式內(nèi)存分配函數(shù)

class Resource ; // 代表需要被封裝的資源類

class NoHashObject

{

private:

Resource* ptr ;// 指向被封裝的資源

... ... // 其它數(shù)據(jù)成員

void* operator new(size_t size) // 非嚴(yán)格實(shí)現(xiàn),僅作示意之用

{

return malloc(size) ;

}

void operator delete(void* pp) // 非嚴(yán)格實(shí)現(xiàn),僅作示意之用

{

free(pp) ;

}

public:

NoHashObject()

{

// 此處可以獲得需要封裝的資源,并讓 ptr 指針指向該資源

ptr = new Resource() ;

}

~NoHashObject()

{

delete ptr ; // 釋放封裝的資源

}

};

NoHashObject 現(xiàn)在就是一個(gè)禁止堆對(duì)象的類了,如果你寫(xiě)下如下代碼:

NoHashObject* fp = new NoHashObject() ; // 編譯期錯(cuò)誤!

delete fp ;

上面代碼會(huì)產(chǎn)生編譯期錯(cuò)誤。好了,現(xiàn)在你已經(jīng)知道了如何設(shè)計(jì)一個(gè)禁止堆對(duì)象的類了,你也許和我一樣有這樣的疑問(wèn),難道 在類 NoHashObject 的定義不能改變的情況下,就一定不能產(chǎn)生該類型的堆對(duì)象了嗎?不,還是有辦法的,我稱之為“暴力破解法”。 C++ 是如此地強(qiáng)大,強(qiáng)大到你可以用它做你想做的任何事情。這里主要用到的是技巧是指針類型的強(qiáng)制轉(zhuǎn)換。

void main(void)

{

char* temp = new char[sizeof(NoHashObject)] ;

// 強(qiáng)制類型轉(zhuǎn)換,現(xiàn)在 ptr 是一個(gè)指向 NoHashObject 對(duì)象的指針

NoHashObject* obj_ptr = (NoHashObject*)temp ;

temp = NULL ; // 防止通過(guò) temp 指針修改 NoHashObject 對(duì)象

// 再一次強(qiáng)制類型轉(zhuǎn)換,讓 rp 指針指向堆中 NoHashObject 對(duì)象的 ptr 成員

Resource* rp = (Resource*)obj_ptr ;

// 初始化 obj_ptr 指向的 NoHashObject 對(duì)象的 ptr 成員

rp = new Resource() ;

// 現(xiàn)在可以通過(guò)使用 obj_ptr 指針使用堆中的 NoHashObject 對(duì)象成員了

... ...

delete rp ;// 釋放資源

temp = (char*)obj_ptr ;

obj_ptr = NULL ;// 防止懸掛指針產(chǎn)生

delete [] temp ;// 釋放 NoHashObject 對(duì)象所占的堆空間。

}

上面的實(shí)現(xiàn)是麻煩的,而且這種實(shí)現(xiàn)方式幾乎不會(huì)在實(shí)踐中使用,但是我還是寫(xiě)出來(lái)路,因?yàn)槔斫馑?,?duì)于我們理解 C++ 內(nèi)存對(duì)象是有好處的。對(duì)于上面的這么多強(qiáng)制類型轉(zhuǎn)換,其最根本的是什么了?我們可以這樣理解:

某塊內(nèi)存中的數(shù)據(jù)是不變的,而類型就是我們戴上的眼鏡,當(dāng)我們戴上一種眼鏡后,我們就會(huì)用對(duì)應(yīng)的類型來(lái)解釋內(nèi)存中的數(shù)據(jù),這樣不同的解釋就得到了不同的信息。

所謂強(qiáng)制類型轉(zhuǎn)換實(shí)際上就是換上另一副眼鏡后再來(lái)看同樣的那塊內(nèi)存數(shù)據(jù)。

另外要提醒的是,不同的編譯器對(duì)對(duì)象的成員數(shù)據(jù)的布局安排可能是不一樣的,比如,大多數(shù)編譯器將 NoHashObject ptr 指針成員安排在對(duì)象空間的頭 4 個(gè)字節(jié),這樣才會(huì)保證下面這條語(yǔ)句的轉(zhuǎn)換動(dòng)作像我們預(yù)期的那樣執(zhí)行:

Resource* rp = (Resource*)obj_ptr ;

但是,并不一定所有的編譯器都是如此。

既然我們可以禁止產(chǎn)生某種類型的堆對(duì)象,那么可以設(shè)計(jì)一個(gè)類,使之不能產(chǎn)生棧對(duì)象嗎?當(dāng)然可以。

五.禁止產(chǎn)生棧對(duì)象

前面已經(jīng)提到了,創(chuàng)建棧對(duì)象時(shí)會(huì)移動(dòng)棧頂指針以“挪出”適當(dāng)大小的空間,然后在這個(gè)空間上直接調(diào)用對(duì)應(yīng)的構(gòu)造函數(shù)以形成一個(gè)棧對(duì)象,而當(dāng)函數(shù)返回時(shí),會(huì)調(diào)用其析構(gòu)函數(shù)釋放這個(gè)對(duì)象,然后再調(diào)整棧頂指針收回那塊棧內(nèi)存。在這個(gè)過(guò)程中是不需要 operator new/delete 操作的,所以將 operator new/delete 設(shè)置為 private 不能達(dá)到目的。當(dāng)然從上面的敘述中,你也許已經(jīng)想到了:將構(gòu)造函數(shù)或析構(gòu)函數(shù)設(shè)為私有的,這樣系統(tǒng)就不能調(diào)用構(gòu)造 / 析構(gòu)函數(shù)了,當(dāng)然就不能在棧中生成對(duì)象了。

這樣的確可以,而且我也打算采用這種方案。但是在此之前,有一點(diǎn)需要考慮清楚 , 那就是,如果我們將構(gòu)造函數(shù)設(shè)置為私有,那么我們也就不能用 new 來(lái)直接產(chǎn)生堆對(duì)象了,因?yàn)? new 在為對(duì)象分配空間后也會(huì)調(diào)用它的構(gòu)造函數(shù)啊。所以,我打算只將析構(gòu)函數(shù)設(shè)置為 private 。再進(jìn)一步,將析構(gòu)函數(shù)設(shè)為 private 除了會(huì)限制棧對(duì)象生成外,還有其它影響嗎?是的,這還會(huì)限制繼承。

如果一個(gè)類不打算作為基類,通常采用的方案就是將其析構(gòu)函數(shù)聲明為 private 。

為了限制棧對(duì)象,卻不限制繼承,我們可以將析構(gòu)函數(shù)聲明為 protected ,這樣就兩全其美了。如下代碼所示:

class NoStackObject

{

protected:

~NoStackObject() { }

public:

void destroy()

{

delete this ;// 調(diào)用保護(hù)析構(gòu)函數(shù)

}

};

接著,可以像這樣使用 NoStackObject 類:

NoStackObject* hash_ptr = new NoStackObject() ;

... ... // 對(duì) hash_ptr 指向的對(duì)象進(jìn)行操作

hash_ptr->destroy() ;

呵呵,是不是覺(jué)得有點(diǎn)怪怪的,我們用 new 創(chuàng)建一個(gè)對(duì)象,卻不是用 delete 去刪除它,而是要用 destroy 方法。很顯然,用戶是不習(xí)慣這種怪異的使用方式的。所以,我決定將構(gòu)造函數(shù)也設(shè)為 private protected 。這又回到了上面曾試圖避免的問(wèn)題,即不用 new ,那么該用什么方式來(lái)生成一個(gè)對(duì)象了? 我們可以用間接的辦法完成,即讓這個(gè)類提供一個(gè) static 成員函數(shù)專門(mén)用于產(chǎn)生該類型的堆對(duì)象。(設(shè)計(jì)模式中的 singleton 模式就可以用這種方式實(shí)現(xiàn)。)讓我們來(lái)看看:

class NoStackObject

{

protected:

NoStackObject() { }

~NoStackObject() { }

public:

static NoStackObject* creatInstance()

{

return new NoStackObject() ;// 調(diào)用保護(hù)的構(gòu)造函數(shù)

}

void destroy()

{

delete this ;// 調(diào)用保護(hù)的析構(gòu)函數(shù)

}

};

現(xiàn)在可以這樣使用 NoStackObject 類了:

NoStackObject* hash_ptr = NoStackObject::creatInstance() ;

... ... // 對(duì) hash_ptr 指向的對(duì)象進(jìn)行操作

hash_ptr->destroy() ;

hash_ptr = NULL ; // 防止使用懸掛指針

現(xiàn)在感覺(jué)是不是好多了,生成對(duì)象和釋放對(duì)象的操作一致了。

ok ,講到這里,已經(jīng)涉及了較多的東西,如果要把內(nèi)存對(duì)象講得更深入更全面,那可能需要寫(xiě)成一本書(shū)了,而就我自己的功力而言,可能是很難完全把握的。如果上面所寫(xiě)的能使你有所收獲或啟發(fā),我就滿足了。如果你要更進(jìn)一步去了解內(nèi)存對(duì)象方面的知識(shí),那么我可以推薦你看看《深入探索 C++ 對(duì)象模型》這本書(shū)。

C++內(nèi)存對(duì)象大會(huì)戰(zhàn)


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦!??!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 视频一区 精品自拍 | 日日摸夜夜爽日日摸视频 | 国产综合欧美 | 亚洲成人精品在线观看 | 久久精品小视频 | 五月天色婷婷在线 | 一级观看免费完整版视频 | 日韩欧美亚洲 | 成人免费大片a毛片 | 亚洲国产七七久久桃花 | 午夜在线免费观看视频 | 国产成年人网站 | 国产成人综合自拍 | 亚洲香蕉毛片久久网站老妇人 | 日日骚视频 | 亚洲一区视频在线 | 欧美一区二区三区免费观看视频 | 成人亚洲一区二区三区 | 91视频 - 88av | 日本免费视频在线观看 | 久草热久草视频 | 精品三级在线 | 日本高清天码一区在线播放 | 国产成人福利视频在线观看 | 色婷婷精品国产一区二区三区 | 天天干天天在线 | 黄a在线观看 | 午夜欧美一区二区三区在线播放 | 很黄很粗很湿很刺激的视频 | jizzzxxxxhd | Jizjizjizjiz日本护士水多 | 国产精品蜜臂在线观看 | 免费一级特黄3大片视频 | 亚洲精品电影在线观看 | 亚洲精品视频在线 | 看特级毛片 | 欧美日韩中文字幕在线视频 | 国产成人一区二区三区电影 | 国产天堂网 | 国产精品尤物在线 | 亚欧免费视频一区二区三区 |