C 記憶體物件大會戰2

2021-06-20 18:50:45 字數 3014 閱讀 6206



四.禁止產生堆物件

上面已經提到,你決定禁止產生某種型別的堆物件,這時你可以自己建立乙個資源封裝類,該類物件只能在棧中產生,這樣就能在異常的情況下自動釋放封裝的資源。

那麼怎樣禁止產生堆物件了?我們已經知道,產生堆物件的唯一方法是使用new操作,如果我們禁止使用new不就行了麼。再進一步,new操作執行時會呼叫operator new,而operator new是可以過載的。方法有了,就是使new operator 為private,為了對稱,最好將operator delete也過載為private。現在,你也許又有疑問了,難道建立棧物件不需要呼叫new嗎?是的,不需要,因為建立棧物件不需要搜尋記憶體,而是直接調整堆疊指標,將物件壓棧,而operator new的主要任務是搜尋合適的堆記憶體,為堆物件分配空間,這在上面已經提到過了。好,讓我們看看下面的示例**:

#include //需要用到c式記憶體分配函式

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

class nohashobject

void operator delete(void* pp) //非嚴格實現,僅作示意之用

public:

nohashobject()

~nohashobject() };

nohashobject現在就是乙個禁止堆物件的類了,如果你寫下如下**:

nohashobject* fp = new nohashobject() ; //編譯期錯誤!

delete fp ;

上面**會產生編譯期錯誤。好了,現在你已經知道了如何設計乙個禁止堆物件的類了,你也許和我一樣有這樣的疑問,難道在類nohashobject的定義不能改變的情況下,就一定不能產生該型別的堆物件了嗎?不,還是有辦法的,我稱之為「暴力破解法」。c++是如此地強大,強大到你可以用它做你想做的任何事情。這裡主要用到的是技巧是指標型別的強制轉換。

void main(void)

上面的實現是麻煩的,而且這種實現方式幾乎不會在實踐中使用,但是我還是寫出來路,因為理解它,對於我們理解c++記憶體物件是有好處的。對於上面的這麼多強制型別轉換,其最根本的是什麼了?我們可以這樣理解:

某塊記憶體中的資料是不變的,而型別就是我們戴上的眼鏡,當我們戴上一種眼鏡後,我們就會用對應的型別來解釋記憶體中的資料,這樣不同的解釋就得到了不同的資訊。

所謂強制型別轉換實際上就是換上另一副眼鏡後再來看同樣的那塊記憶體資料。

另外要提醒的是,不同的編譯器對物件的成員資料的布局安排可能是不一樣的,比如,大多數編譯器將nohashobject的ptr指標成員安排在物件空間的頭4個位元組,這樣才會保證下面這條語句的轉換動作像我們預期的那樣執行:

resource* rp = (resource*)obj_ptr ;

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

既然我們可以禁止產生某種型別的堆物件,那麼可以設計乙個類,使之不能產生棧物件嗎?當然可以。

五.禁止產生棧物件

前面已經提到了,建立棧物件時會移動棧頂指標以「挪出」適當大小的空間,然後在這個空間上直接呼叫對應的建構函式以形成乙個棧物件,而當函式返回時,會呼叫其析構函式釋放這個物件,然後再調整棧頂指標收回那塊棧記憶體。在這個過程中是不需要operator new/delete操作的,所以將operator new/delete設定為private不能達到目的。當然從上面的敘述中,你也許已經想到了:將建構函式或析構函式設為私有的,這樣系統就不能呼叫構造/析構函式了,當然就不能在棧中生成物件了。

這樣的確可以,而且我也打算採用這種方案。但是在此之前,有一點需要考慮清楚,那就是,如果我們將建構函式設定為私有,那麼我們也就不能用new來直接產生堆物件了,因為new在為物件分配空間後也會呼叫它的建構函式啊。所以,我打算只將析構函式設定為private。再進一步,將析構函式設為private除了會限制棧物件生成外,還有其它影響嗎?是的,這還會限制繼承。

如果乙個類不打算作為基類,通常採用的方案就是將其析構函式宣告為private。

為了限制棧物件,卻不限制繼承,我們可以將析構函式宣告為protected,這樣就兩全其美了。如下**所示:

class nostackobject

public:

void destroy() };

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

nostackobject* hash_ptr = new nostackobject() ;

... ... //對hash_ptr指向的物件進行操作

hash_ptr->destroy() ;

呵呵,是不是覺得有點怪怪的,我們用new建立乙個物件,卻不是用delete去刪除它,而是要用destroy方法。很顯然,使用者是不習慣這種怪異的使用方式的。所以,我決定將建構函式也設為private或protected。這又回到了上面曾試圖避免的問題,即不用new,那麼該用什麼方式來生成乙個物件了?我們可以用間接的辦法完成,即讓這個類提供乙個static成員函式專門用於產生該型別的堆物件。(設計模式中的singleton模式就可以用這種方式實現。)讓我們來看看:

class nostackobject

~nostackobject()

public:

static nostackobject* creatinstance()

void destroy() };

現在可以這樣使用nostackobject類了:

nostackobject* hash_ptr = nostackobject::creatinstance() ;

... ... //對hash_ptr指向的物件進行操作

hash_ptr->destroy() ;

hash_ptr = null ; //防止使用懸掛指標

現在感覺是不是好多了,生成物件和釋放物件的操作一致了。

ok,講到這裡,已經涉及了較多的東西,如果要把記憶體物件講得更深入更全面,那可能需要寫成一本書了,而就我自己的功力而言,可能是很難完全把握的。如果上面所寫的能使你有所收穫或啟發,我就滿足了。如果你要更進一步去了解記憶體物件方面的知識,那麼我可以推薦你看看《深入探索c++物件模型》這本書。

C 記憶體物件大會戰

先來看看棧。棧,一般用於存放區域性變數或物件,如我們在函式定義中用類似下面語句宣告的物件 type stack object stack object便是乙個棧物件,它的生命期是從定義點開始,當所在函式返回時,生命結束。另外,幾乎所有的臨時物件都是棧物件。比如,下面的函式定義 type fun ty...

2019天搜如何幹?春訓大會戰略詳解篇給你答案

2019天搜如何幹?春訓大會戰略詳解篇給你答案 美好的一年,從展望未來開始,提前謀劃 周密部署才能搶占市場先機。隨著春節假期結束,天蒐以 凝心聚力,跨越蝶變 第十四屆春訓大會正式打響新年第一槍,明確了2019年的戰略方向。那麼,2019年天搜將怎麼衝刺呢?讓我們來詳細解讀。我們所處的環境 回首201...

C 物件模型之記憶體布局(2)

多重繼承 繼承關係大於2,至少有父類,子類,孫子類三代關係。使用vs2017檢視物件記憶體布局如下 class c size 20 0 base class b 0 base class a 0 4 a1 8 a2 12 b1 16 c1 c vftable c meta 0 0 c a1 1 a ...