類如何實現只能靜態分配和只能動態分配

2021-10-09 15:01:48 字數 4506 閱讀 8605

首先,在c++中,類物件的建立方式有兩種:

靜態建立類物件,如a a;這是由編譯器為物件在棧空間中分配記憶體。

動態建立類物件,如a* p = new a;使用new運算子為物件在棧空間分配記憶體。

兩種方式區別:

靜態建立類物件:是指全域性物件,靜態物件,以及分配在棧區域內的物件,編譯器對它們的記憶體分配是在編譯階段就完成了,是通過直接移動棧頂指標,挪出適當的空間,然後在這片記憶體空間上呼叫建構函式形成乙個棧物件。使用這種方法,直接呼叫類的建構函式。

動態建立類物件:分配堆區域內的物件,編譯器對他們的記憶體分配是在執行時動態分配的,(使用new運算子將物件建立在堆空間中。這個過程分為兩步:第一步,執行operator new()函式,找到合適的記憶體進行分配;第二步,呼叫建構函式構造物件,初始化這片記憶體空間。使用這種方法,間接呼叫類的建構函式。注意這裡需要尋找合適記憶體,然後構造,對應是析構的過程。

舉個栗子:

注意:c++是乙個靜態繫結的語言。在編譯過程中,所有的非虛函式呼叫都必須分析完成。即使是虛函式, 也需檢查可訪問性。因此,當在棧(stack)上生成物件時, 物件會自動析構(系統完成), 也就說析構函式必須可以訪問。 而堆上生成物件,由於析構時機由程式設計師控制,所以不一定需要析構函式。 保證了不能在棧上生成物件後, 需要證明能在堆上生成它。這裡onlyheapclass與一般物件唯一的區別在於它的析構函式為私有,delete操作會呼叫析構函式, 所以不能編譯。

解決方法:提供乙個成員函式, 完成delete操作。在成員函式中,析構函式是可以訪問的,當然detele操作也是可以編譯通過。

析構函式私有化的類的設計可以保證只能用new命令在堆(heap)中建立物件,只能動態的去建立物件, 這樣可以自由的控制物件的生命週期,但這樣的類需要提供建立和撤銷的公共介面。

如何禁止產生堆/棧物件?

1. 禁止產生堆物件

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

那麼如何禁止產生堆物件呢?

我們已經知道,產生堆物件的唯一方法是使用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++是如此地強大,強大到你可以用它做你想做的任何事情,這裡主要用到的是技巧是指標型別的強制轉換。

int main()

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

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

所謂強制型別轉換實際上就是換上另一副眼鏡後再來看同樣的那塊記憶體資料。 另外要提醒的是, 不同的編譯器對物件的成員資料的布局安排可能是不一樣的, 比如, 大多數編譯器將nohashobject的ptr指標成員安排在物件空間的頭4個位元組, 這樣才會保證下面這條語句的轉換動作像我們預期的那樣執行:

resource* rp = (resource*)obj_ptr ;

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

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

2. 禁止產生棧物件

前面已經提到了, 建立棧物件時會移動棧頂指標以「挪出」適當大小的空間, 然後在這個空間上直接呼叫對應的建構函式以形成乙個棧物件, 而當函式返回時, 會呼叫其析構函式釋放這個物件, 然後再調整棧頂指標收回那塊棧記憶體. 在這個過程中是不需要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 ; //防止使用懸掛指標

所以,記住:

靜態分配:使用new運算子,物件會被建立在堆上,設new私有即可。

動態分配:將建構函式也設為private或protected

下面展示一些內聯**片

classa~

a()public

:statica*

create()

void

destory()

};classb;

// 注意函式的第乙個引數和返回值都是固定的

void operator delete

(void

* ptr)

;// 過載了new就需要過載delete

public:b

();~

b()}

;

end-----------------------------------

參考:

只能動態 靜態分配類物件

只能動態分配類物件.cpp 定義控制台應用程式的入口點。include stdafx.h include using namespace std 只在堆上建立 把析構函式設定為私有即可,但是這樣的話以它為基類的派生類就不能訪問析構函式來釋放資源了 因此設定成protect 只能在類內或派生類內訪問。...

C 如何實現類物件只能動態分配或只能靜態分配

c 中建立類的物件有兩種方式 1 靜態建立,例如 a a 靜態建立乙個類物件,就是由編譯器為物件在棧空間中分配記憶體。使用這種方法,是直接呼叫類的建構函式。2 動態建立,例如 a p new a 動態建立乙個類物件,就是使用new運算子為物件在堆空間中分配記憶體。這個過程分為兩步 第一步執行oper...

如何實現鏈結只能被點選一次

有時候,只希望 某個鏈結只能被點選一次,怎麼做呢?下面給出3中方法!第一種 利用js在點選後把href變成 把taget變成空。p a onclick var that this settimeout function 0 return true href target blank open goo...