只能在堆或棧上創建對象?

【整理】只能在堆或棧上創建對象?

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)上生成對象

   爲禁止產生某種類型的堆對象, 可以自己創建一個資源封裝類, 該類對象只能在棧中產生,這樣就能在異常的情況下自動釋放封裝的資源.

    產生堆對象的唯一方法是使用new, 禁止使用new就可禁止產生堆對象. 由於new執行時會調用operator new, 而operator new是可重載的, 所以將operator new和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;    
  } 
複製代碼
    對於上面的這麼多強制類型轉換, 其最根本的是什麼了? 可以這樣理解: 某塊內存中的數據是不變的, 而類型就是我們戴上的眼鏡, 當我們戴上一種眼鏡後, 我們就會用對應的類型來解釋內存中的數據,這樣不同的解釋就得到了不同的信息. 所謂強制類型轉換實際上就是換上另一副眼鏡後再來看同樣的那塊內存數據.
    另外要提醒的是,不同的編譯器對對象的成員數據的佈局安排可能是不一樣的, 比如大多數編譯器將NoHashObject的ptr指針成員安排在對象空間的頭4個字節,這樣纔會保證下面這條語句的轉換動作像我們預期的那樣執行: Resource* rp = (Resource*)obj_ptr; 但並不一定所有的編譯器都是如此.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章