如何保證只能在堆(heap)上建立物件?
阿新 • • 發佈:2019-01-24
上面的實現是麻煩的, 而且這種實現方式幾乎不會在實踐中使用, 但是我還是寫出來路, 因為理解它, 對於我們理解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 ; //防止使用懸掛指標
某塊記憶體中的資料是不變的, 而型別就是我們戴上的眼鏡, 當我們戴上一種眼鏡後, 我們就會用對應的型別來解釋記憶體中的資料, 這樣不同的解釋就得到了不同的資訊.
所謂強制型別轉換實際上就是換上另一副眼鏡後再來看同樣的那塊記憶體資料.
另外要提醒的是, 不同的編譯器對物件的成員資料的佈局安排可能是不一樣的, 比如, 大多數編譯器將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 ; //防止使用懸掛指標