【整理】只能在堆或棧上創建對象?
1. 只能在堆(heap)上創建對象/禁止產生棧(stack)對象
創建棧對象時會移動棧頂指針以“挪出”適當大小的空間, 再在這個空間上直接調用對應的構造函數以形成一個棧對象, 而當函數返回時會調用其析構函數釋放這個對象, 再調整棧頂指針收回那塊棧內存, 在這個過程中是不需要operator new/delete操作的, 所以將operator new/delete設置爲private不能達到禁止產生棧(stack)對象的目的.
把析構函數定義爲private訪問權限, 就可以保證只能在堆(heap)上創建(new)一個新的類對象.析構函數私有化的類的設計可以保證只能用new命令在堆(heap)中創建對象, 只能動態的去創建對象, 這樣可以自由的控制對象的生命週期, 但這樣的類需要提供創建和撤銷的公共接口.
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; }
解析:C++是一個靜態綁定的語言. 在編譯過程中, 所有的非虛函數調用都必須分析完成. 即使是虛函數, 也需檢查可訪問性. 在棧(stack)上生成對象時, 對象會自動析構, 即析構函數必須可以訪問. 而在堆(heap)上生成對象, 由於析構時機由程序員控制,所以不一定需要析構函數.
將析構函數設爲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, 但需要讓該類提供一個static成員函數專門用於產生該類型的堆對象. (設計模式中的singleton模式就可以用這種方式實現.)讓我們來看看:
class NoStackObject { protected: NoStackObject() { } ~NoStackObject() { } public: static NoStackObject* creatInstance() { return new NoStackObject() ;//調用保護的構造函數 } void destroy() { delete this ;//調用保護的析構函數 } }; NoStackObject* hash_ptr = NoStackObject::creatInstance(); ... ... //對hash_ptr指向的對象進行操作 hash_ptr->destroy(); hash_ptr = NULL; //防止使用懸掛指針
2. 只能在棧(stack)上生成對象
爲禁止產生某種類型的堆對象, 可以自己創建一個資源封裝類, 該類對象只能在棧中產生,這樣就能在異常的情況下自動釋放封裝的資源.
#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* fp = new NoHashObject(); // 編譯期錯誤!
delete fp;
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; }