只在堆上或只在栈上定义对象
生活随笔
收集整理的這篇文章主要介紹了
只在堆上或只在栈上定义对象
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
轉自:http://blog.csdn.net/g5dsk/article/details/4775144
代碼:
[cpp]?view plaincopyprint? class?OnlyHeapClass?? {?? public:?? ????OnlyHeapClass()?? ????{?? ????}?? ?? ????void?Destroy()?? ????{?? ????????delete?this;?//?等效于"OnlyHeapClass::~OnlyHeapClass();",?寫?? ?????????????????????//?成"OnlyHeapClass::~OnlyHeapClass();"更容易理?? ?????????????????????//?解public成員函數調用private析構函數.?? ????}?? ?? private:?? ????~OnlyHeapClass()?? ????{?? ????}?? };?? ?? int?main()?? {?? ????OnlyHeapClass?*pInst?=?new?OnlyHeapClass;?? ?? ????pInst?->Destroy();?//?如果類中沒有定義Destroy()函數,?而在這里用"delete?pInst;"代?? ???????????????????????//?替"pInst->Destroy();",?則會報錯.?因為"delete?pInst;"會去調?? ???????????????????????//?用類的析構函數,?而在類域外調用類的private成員函數必然會報錯.?? ?? ????return?0;?? }??
總結:??把析構函數定義為private訪問權限, 就可以保證只能在堆(heap)上創建(new)一個新的類對象.
????
??? 原因是C++是一個靜態綁定的語言. 在編譯過程中, 所有的非虛函數調用都必須分析完成. 即使是虛函數, 也需檢查可訪問性. 因些, 當在棧(stack)上生成對象時, 對象會自動析構, 也就說析構函數必須可以訪問. 而堆上生成對象, 由于析構時機由程序員控制, 所以不一定需要析構函數. 保證了不能在棧上生成對象后, 需要證明能在堆上生成它. 這里OnlyHeapClass與一般對象唯一的區別在于它的析構函數為私有, delete操作會調用析構函數, 所以不能編譯.
??? 那么如何釋放它呢? 答案也很簡單, 提供一個成員函數, 完成delete操作. 在成員函數中, 析構函數是可以訪問的, 當然detele操作也是可以編譯通過. [cpp]?view plaincopyprint? void?OnlyHeapClass::Destroy()??? {??? ????delete?this;??? }???
??? 另外重載delete, new為私有可以達到要求對象創建于棧上的目的, 用placement new也可以創建在棧上. /?下面灰色字體系轉載幫助理解之用?/??
??? 上面已經提到, 你決定禁止產生某種類型的堆對象, 這時你可以自己創建一個資源封裝類, 該類對象只能在棧中產生, 這樣就能在異常的情況下自動釋放封裝的資源. ??? 那么怎樣禁止產生堆對象了? 我們已經知道, 產生堆對象的唯一方法是使用new操作, 如果我們禁止使用new不就行了么. 再進一步, new操作執行時會調用operator new, 而operator new是可以重載的. 方法有了, 就是使new operator為private, 為了對稱, 最好將operator delete也重載為private. 現在, 你也許又有疑問了, 難道創建棧對象不需要調用new嗎? 是的, 不需要, 因為創建棧對象不需要搜索內存, 而是直接調整堆棧指針, 將對象壓棧, 而operator new的主要任務是搜索合適的堆內存, 為堆對象分配空間, 這在上面已經提到過了. 好, 讓我們看看下面的示例代碼:??? ? #include <stdlib.h>?? // 需要用到C式內存分配函數???
? class Resource ;?? // 代表需要被封裝的資源類???
? class NoHashObject???
? {???
? private:???
? Resource *ptr ; // 指向被封裝的資源???
? // ...? //其它數據成員?
??
? void*?? operator?? new(size_t?? size) //非嚴格實現, 僅作示意之用???
? {???
? return malloc(size);???
? }?
??
? void operator delete(void* pp) //非嚴格實現, 僅作示意之用???
? {???
? free(pp);???
? }
??
? public:???
? NoHashObject()???
? {???
? // 此處可以獲得需要封裝的資源, 并讓ptr指針指向該資源???
? ptr = new Resource();???
? }?
??
? ~NoHashObject()???
? {???
? delete ptr;?? // 釋放封裝的資源???
? }???
? };?????
????
??? NoHashObject現在就是一個禁止堆對象的類了, 如果你寫下如下代碼:????
????
? NoHashObject* fp = new NoHashObject(); // 編譯期錯誤!???
? delete fp;?
????
??? 上面代碼會產生編譯期錯誤. 好了, 現在你已經知道了如何設計一個禁止堆對象的類了, 你也許和我一樣有這樣的疑問, 難道在類NoHashObject的定義不能改變的情況下, 就一定不能產生該類型的堆對象了嗎? 不, 還是有辦法的, 我稱之為“暴力破解法”. C++是如此地強大, 強大到你可以用它做你想做的任何事情. 這里主要用到的是技巧是指針類型的強制轉換.????
????
? int main()???
? {???
? char*?? temp?? =?? new?? char[sizeof(NoHashObject)]?? ;???
????
? //強制類型轉換, 現在ptr是一個指向NoHashObject對象的指針???
? NoHashObject*?? obj_ptr?? =?? (NoHashObject*)temp?? ;???
????
? temp?? =?? NULL?? ;?? //防止通過temp指針修改NoHashObject對象???
????
? //再一次強制類型轉換, 讓rp指針指向堆中NoHashObject對象的ptr成員???
? Resource*?? rp?? =?? (Resource*)obj_ptr?? ;???
????
? //初始化obj_ptr指向的NoHashObject對象的ptr成員???
? rp?? =?? new?? Resource()?? ;???
? //現在可以通過使用obj_ptr指針使用堆中的NoHashObject對象成員了???
? ...?? ...???
????
? delete?? rp?? ;//釋放資源???
? temp?? =?? (char*)obj_ptr?? ;???
? obj_ptr?? =?? NULL?? ;//防止懸掛指針產生???
? delete?? []?? temp?? ;//釋放NoHashObject對象所占的堆空間.??
????
??? return 0;????
? }???? ??? 上面的實現是麻煩的, 而且這種實現方式幾乎不會在實踐中使用, 但是我還是寫出來路, 因為理解它, 對于我們理解C++內存對象是有好處的. 對于上面的這么多強制類型轉換, 其最根本的是什么了? 我們可以這樣理解:????
????
??? 某塊內存中的數據是不變的, 而類型就是我們戴上的眼鏡, 當我們戴上一種眼鏡后, 我們就會用對應的類型來解釋內存中的數據, 這樣不同的解釋就得到了不同的信息.????
????
??? 所謂強制類型轉換實際上就是換上另一副眼鏡后再來看同樣的那塊內存數據.????
????????
??? 另外要提醒的是, 不同的編譯器對對象的成員數據的布局安排可能是不一樣的, 比如, 大多數編譯器將NoHashObject的ptr指針成員安排在對象空間的頭4個字節, 這樣才會保證下面這條語句的轉換動作像我們預期的那樣執行:????
????
? Resource*?? rp?? =?? (Resource*)obj_ptr?? ;?????
????
??? 但是, 并不一定所有的編譯器都是如此.????
????
??? 既然我們可以禁止產生某種類型的堆對象, 那么可以設計一個類, 使之不能產生棧對象嗎? 當然可以.????
????
??? 五.禁止產生棧對象???
????
??? 前面已經提到了, 創建棧對象時會移動棧頂指針以“挪出”適當大小的空間, 然后在這個空間上直接調用對應的構造函數以形成一個棧對象, 而當函數返回時, 會調用其析構函數釋放這個對象, 然后再調整棧頂指針收回那塊棧內存. 在這個過程中是不需要operator?? new/delete操作的, 所以將operator?? new/delete設置為private不能達到目的. 當然從上面的敘述中, 你也許已經想到了: 將構造函數或析構函數設為私有的, 這樣系統就不能調用構造/析構函數了, 當然就不能在棧中生成對象了.????
????
??? 這樣的確可以, 而且我也打算采用這種方案. 但是在此之前, 有一點需要考慮清楚,那就是, 如果我們將構造函數設置為私有, 那么我們也就不能用new來直接產生堆對象了, 因為new在為對象分配空間后也會調用它的構造函數啊. 所以, 我打算只將析構函數設置為private. 再進一步, 將析構函數設為private除了會限制棧對象生成外, 還有其它影響嗎? 是的, 這還會限制繼承.????
????
??? 如果一個類不打算作為基類, 通常采用的方案就是將其析構函數聲明為private.????
????
??? 為了限制棧對象, 卻不限制繼承, 我們可以將析構函數聲明為protected, 這樣就兩全其美了. 如下代碼所示:????
????
? class?? NoStackObject???
? {???
? protected:???
? ~NoStackObject()?? {?? }???
? public:???
? void?? destroy()???
? {???
? delete?? this?? ;//調用保護析構函數???
? }???
? };?????
????
??? 接著, 可以像這樣使用NoStackObject類:????
????
? NoStackObject*?? hash_ptr?? =?? new?? NoStackObject()?? ;???
? ...?? ...?? //對hash_ptr指向的對象進行操作???
? hash_ptr->destroy()?? ;?????
????
??? 呵呵, 是不是覺得有點怪怪的, 我們用new創建一個對象, 卻不是用delete去刪除它, 而是要用destroy方法. 很顯然, 用戶是不習慣這種怪異的使用方式的. 所以, 我決定將構造函數也設為private或protected. 這又回到了上面曾試圖避免的問題, 即不用new, 那么該用什么方式來生成一個對象了? 我們可以用間接的辦法完成, 即讓這個類提供一個static成員函數專門用于產生該類型的堆對象. (設計模式中的singleton模式就可以用這種方式實現. )讓我們來看看:????
????
? class?? NoStackObject???
? {???
? protected:???
? NoStackObject()?? {?? }???
? ~NoStackObject()?? {?? }???
? public:???
? static?? NoStackObject*?? creatInstance()???
? {???
? return?? new?? NoStackObject()?? ;//調用保護的構造函數???
? }???
? void?? destroy()???
? {???
? delete?? this?? ;//調用保護的析構函數???
? }???
? };?????
????
??? 現在可以這樣使用NoStackObject類了:????
????
? NoStackObject*?? hash_ptr?? =?? NoStackObject::creatInstance()?? ;???
? ...?? ...?? //對hash_ptr指向的對象進行操作???
? hash_ptr->destroy()?? ;???
? hash_ptr?? =?? NULL?? ;?? //防止使用懸掛指針?????
????
??? 現在感覺是不是好多了, 生成對象和釋放對象的操作一致了. ??
??? 原因是C++是一個靜態綁定的語言. 在編譯過程中, 所有的非虛函數調用都必須分析完成. 即使是虛函數, 也需檢查可訪問性. 因些, 當在棧(stack)上生成對象時, 對象會自動析構, 也就說析構函數必須可以訪問. 而堆上生成對象, 由于析構時機由程序員控制, 所以不一定需要析構函數. 保證了不能在棧上生成對象后, 需要證明能在堆上生成它. 這里OnlyHeapClass與一般對象唯一的區別在于它的析構函數為私有, delete操作會調用析構函數, 所以不能編譯.
??? 那么如何釋放它呢? 答案也很簡單, 提供一個成員函數, 完成delete操作. 在成員函數中, 析構函數是可以訪問的, 當然detele操作也是可以編譯通過. [cpp]?view plaincopyprint?
析構函數私有化的類的設計可以保證只能用new命令在堆(heap)中創建對象, 只能動態的去創建對象, 這樣可以自由的控制對象的生命周期. 但是, 這樣的類需要提供創建和撤銷的公共接口.?
??? 另外重載delete, new為私有可以達到要求對象創建于棧上的目的, 用placement new也可以創建在棧上.
[cpp]?view plaincopyprint?
四.禁止產生堆對象
??? 上面已經提到, 你決定禁止產生某種類型的堆對象, 這時你可以自己創建一個資源封裝類, 該類對象只能在棧中產生, 這樣就能在異常的情況下自動釋放封裝的資源. ??? 那么怎樣禁止產生堆對象了? 我們已經知道, 產生堆對象的唯一方法是使用new操作, 如果我們禁止使用new不就行了么. 再進一步, new操作執行時會調用operator new, 而operator new是可以重載的. 方法有了, 就是使new operator為private, 為了對稱, 最好將operator delete也重載為private. 現在, 你也許又有疑問了, 難道創建棧對象不需要調用new嗎? 是的, 不需要, 因為創建棧對象不需要搜索內存, 而是直接調整堆棧指針, 將對象壓棧, 而operator new的主要任務是搜索合適的堆內存, 為堆對象分配空間, 這在上面已經提到過了. 好, 讓我們看看下面的示例代碼:??? ? #include <stdlib.h>?? // 需要用到C式內存分配函數???
? class Resource ;?? // 代表需要被封裝的資源類???
? class NoHashObject???
? {???
? private:???
? Resource *ptr ; // 指向被封裝的資源???
? // ...? //其它數據成員?
??
? void*?? operator?? new(size_t?? size) //非嚴格實現, 僅作示意之用???
? {???
? return malloc(size);???
? }?
??
? void operator delete(void* pp) //非嚴格實現, 僅作示意之用???
? {???
? free(pp);???
? }
??
? public:???
? NoHashObject()???
? {???
? // 此處可以獲得需要封裝的資源, 并讓ptr指針指向該資源???
? ptr = new Resource();???
? }?
??
? ~NoHashObject()???
? {???
? delete ptr;?? // 釋放封裝的資源???
? }???
? };?????
????
??? NoHashObject現在就是一個禁止堆對象的類了, 如果你寫下如下代碼:????
????
? NoHashObject* fp = new NoHashObject(); // 編譯期錯誤!???
? delete fp;?
????
??? 上面代碼會產生編譯期錯誤. 好了, 現在你已經知道了如何設計一個禁止堆對象的類了, 你也許和我一樣有這樣的疑問, 難道在類NoHashObject的定義不能改變的情況下, 就一定不能產生該類型的堆對象了嗎? 不, 還是有辦法的, 我稱之為“暴力破解法”. C++是如此地強大, 強大到你可以用它做你想做的任何事情. 這里主要用到的是技巧是指針類型的強制轉換.????
????
? int main()???
? {???
? char*?? temp?? =?? new?? char[sizeof(NoHashObject)]?? ;???
????
? //強制類型轉換, 現在ptr是一個指向NoHashObject對象的指針???
? NoHashObject*?? obj_ptr?? =?? (NoHashObject*)temp?? ;???
????
? temp?? =?? NULL?? ;?? //防止通過temp指針修改NoHashObject對象???
????
? //再一次強制類型轉換, 讓rp指針指向堆中NoHashObject對象的ptr成員???
? Resource*?? rp?? =?? (Resource*)obj_ptr?? ;???
????
? //初始化obj_ptr指向的NoHashObject對象的ptr成員???
? rp?? =?? new?? Resource()?? ;???
? //現在可以通過使用obj_ptr指針使用堆中的NoHashObject對象成員了???
? ...?? ...???
????
? delete?? rp?? ;//釋放資源???
? temp?? =?? (char*)obj_ptr?? ;???
? obj_ptr?? =?? NULL?? ;//防止懸掛指針產生???
? delete?? []?? temp?? ;//釋放NoHashObject對象所占的堆空間.??
????
??? return 0;????
? }???? ??? 上面的實現是麻煩的, 而且這種實現方式幾乎不會在實踐中使用, 但是我還是寫出來路, 因為理解它, 對于我們理解C++內存對象是有好處的. 對于上面的這么多強制類型轉換, 其最根本的是什么了? 我們可以這樣理解:????
????
??? 某塊內存中的數據是不變的, 而類型就是我們戴上的眼鏡, 當我們戴上一種眼鏡后, 我們就會用對應的類型來解釋內存中的數據, 這樣不同的解釋就得到了不同的信息.????
????
??? 所謂強制類型轉換實際上就是換上另一副眼鏡后再來看同樣的那塊內存數據.????
????????
??? 另外要提醒的是, 不同的編譯器對對象的成員數據的布局安排可能是不一樣的, 比如, 大多數編譯器將NoHashObject的ptr指針成員安排在對象空間的頭4個字節, 這樣才會保證下面這條語句的轉換動作像我們預期的那樣執行:????
????
? Resource*?? rp?? =?? (Resource*)obj_ptr?? ;?????
????
??? 但是, 并不一定所有的編譯器都是如此.????
????
??? 既然我們可以禁止產生某種類型的堆對象, 那么可以設計一個類, 使之不能產生棧對象嗎? 當然可以.????
????
??? 五.禁止產生棧對象???
????
??? 前面已經提到了, 創建棧對象時會移動棧頂指針以“挪出”適當大小的空間, 然后在這個空間上直接調用對應的構造函數以形成一個棧對象, 而當函數返回時, 會調用其析構函數釋放這個對象, 然后再調整棧頂指針收回那塊棧內存. 在這個過程中是不需要operator?? new/delete操作的, 所以將operator?? new/delete設置為private不能達到目的. 當然從上面的敘述中, 你也許已經想到了: 將構造函數或析構函數設為私有的, 這樣系統就不能調用構造/析構函數了, 當然就不能在棧中生成對象了.????
????
??? 這樣的確可以, 而且我也打算采用這種方案. 但是在此之前, 有一點需要考慮清楚,那就是, 如果我們將構造函數設置為私有, 那么我們也就不能用new來直接產生堆對象了, 因為new在為對象分配空間后也會調用它的構造函數啊. 所以, 我打算只將析構函數設置為private. 再進一步, 將析構函數設為private除了會限制棧對象生成外, 還有其它影響嗎? 是的, 這還會限制繼承.????
????
??? 如果一個類不打算作為基類, 通常采用的方案就是將其析構函數聲明為private.????
????
??? 為了限制棧對象, 卻不限制繼承, 我們可以將析構函數聲明為protected, 這樣就兩全其美了. 如下代碼所示:????
????
? class?? NoStackObject???
? {???
? protected:???
? ~NoStackObject()?? {?? }???
? public:???
? void?? destroy()???
? {???
? delete?? this?? ;//調用保護析構函數???
? }???
? };?????
????
??? 接著, 可以像這樣使用NoStackObject類:????
????
? NoStackObject*?? hash_ptr?? =?? new?? NoStackObject()?? ;???
? ...?? ...?? //對hash_ptr指向的對象進行操作???
? hash_ptr->destroy()?? ;?????
????
??? 呵呵, 是不是覺得有點怪怪的, 我們用new創建一個對象, 卻不是用delete去刪除它, 而是要用destroy方法. 很顯然, 用戶是不習慣這種怪異的使用方式的. 所以, 我決定將構造函數也設為private或protected. 這又回到了上面曾試圖避免的問題, 即不用new, 那么該用什么方式來生成一個對象了? 我們可以用間接的辦法完成, 即讓這個類提供一個static成員函數專門用于產生該類型的堆對象. (設計模式中的singleton模式就可以用這種方式實現. )讓我們來看看:????
????
? class?? NoStackObject???
? {???
? protected:???
? NoStackObject()?? {?? }???
? ~NoStackObject()?? {?? }???
? public:???
? static?? NoStackObject*?? creatInstance()???
? {???
? return?? new?? NoStackObject()?? ;//調用保護的構造函數???
? }???
? void?? destroy()???
? {???
? delete?? this?? ;//調用保護的析構函數???
? }???
? };?????
????
??? 現在可以這樣使用NoStackObject類了:????
????
? NoStackObject*?? hash_ptr?? =?? NoStackObject::creatInstance()?? ;???
? ...?? ...?? //對hash_ptr指向的對象進行操作???
? hash_ptr->destroy()?? ;???
? hash_ptr?? =?? NULL?? ;?? //防止使用懸掛指針?????
????
??? 現在感覺是不是好多了, 生成對象和釋放對象的操作一致了. ??
總結
以上是生活随笔為你收集整理的只在堆上或只在栈上定义对象的全部內容,希望文章能夠幫你解決所遇到的問題。