C++ 內存分配(new,operator new)詳解

參考:C++ 內存分配(new,operator new)詳解函數

如何限制對象只能創建在堆上或者棧上this

new運算符和operator new()

new:指咱們在C++裏一般用到的運算符,好比A* a = new A;  對於new來講,有new和::new之分,前者位於stdspa

operator new():指對new的重載形式,它是一個函數,並非運算符。對於operator new來講,分爲全局重載和類重載,全局重載是void* ::operator new(size_t size),在類中重載形式 void* A::operator new(size_t size)。還要注意的是這裏的operator new()完成的操做通常只是分配內存,事實上系統默認的全局::operator new(size_t size)也只是調用malloc分配內存,而且返回一個void*指針。而構造函數的調用(若是須要)是在new運算符中完成的.net

new和operator new之間的關係

A* a = new A;咱們知道這裏分爲兩步: 1.分配內存,2.調用A()構造對象。事實上,分配內存這一操做就是由operator new(size_t)來完成的,若是類A重載了operator new,那麼將調用A::operator new(size_t ),若是沒有重載,就調用::operator new(size_t ),全局new操做符由C++默認提供。所以前面的兩步也就是: 1.調用operator new 2.調用構造函數。

(1)new :不能被重載,其行爲老是一致的。它先調用operator new分配內存,而後調用構造函數初始化那段內存。指針

new 操做符的執行過程:
1. 調用operator new分配內存 ;
2. 調用構造函數生成類對象;
3. 返回相應指針。code

(2)operator new:要實現不一樣的內存分配行爲,應該重載operator new,而不是new。對象

operator new就像operator + 同樣,是能夠重載的。若是類中沒有重載operator new,那麼調用的就是全局的::operator new來完成堆的分配。同理,operator new[]、operator delete、operator delete[]也是能夠重載的。blog

如何限制對象只能創建在堆上或者棧上

在C++中,類的對象創建分爲兩種,一種是靜態創建,如A a;另外一種是動態創建,如A* ptr=new A;這兩種方式是有區別的。繼承

靜態創建一個類對象,是由編譯器爲對象在棧空間中分配內存,是經過直接移動棧頂指針,挪出適當的空間,而後在這片內存空間上調用構造函數造成一個棧對象。使用這種方法,直接調用類的構造函數。生命週期

動態創建類對象,是使用new運算符將對象創建在堆空間中。這個過程分爲兩步,第一步是執行operator new()函數,在堆空間中搜索合適的內存並進行分配;第二步是調用構造函數構造對象,初始化這片內存空間。這種方法,間接調用類的構造函數。

那麼如何限制類對象只能在堆或者棧上創建呢?下面分別進行討論。

只能創建在堆上(設置析構函數爲Protected)

類對象只能創建在堆上,就是不能靜態創建類對象,即不能直接調用類的構造函數。

容易想到將構造函數設爲私有。在構造函數私有以後,沒法在類外部調用構造函數來構造類對象,只能使用new運算符來創建對象。然而,前面已經說過,new運算符的執行過程分爲兩步,C++提供new運算符的重載,實際上是隻容許重載operator new()函數,而operator()函數用於分配內存,沒法提供構造功能。所以,這種方法不能夠。

當對象創建在棧上面時,是由編譯器分配內存空間的,調用構造函數來構造棧對象。當對象使用完後,編譯器會調用析構函數來釋放棧對象所佔的空間。編譯器管理了對象的整個生命週期。若是編譯器沒法調用類的析構函數,狀況會是怎樣的呢?好比,類的析構函數是私有的,編譯器沒法調用析構函數來釋放內存。因此,編譯器在爲類對象分配棧空間時,會先檢查類的析構函數的訪問性,其實不光是析構函數,只要是非靜態的函數,編譯器都會進行檢查。若是類的析構函數是私有的,則編譯器不會在棧空間上爲類對象分配內存。

所以,將析構函數設爲私有,類對象就沒法創建在棧上了。代碼以下:

class A  
{  
public:  
    A(){}  
    void destory(){delete this;}  
private:  
    ~A(){}  
};  

 試着使用A a;來創建對象,編譯報錯,提示析構函數沒法訪問。這樣就只能使用new操做符來創建對象,構造函數是公有的,能夠直接調用。類中必須提供一個destory函數,來進行內存空間的釋放。類對象使用完成後,必須調用destory函數。

上述方法的一個缺點就是,沒法解決繼承問題。若是A做爲其它類的基類,則析構函數一般要設爲virtual,而後在子類重寫,以實現多態。所以析構函數不能設爲private。還好C++提供了第三種訪問控制,protected。將析構函數設爲protected能夠有效解決這個問題,類外沒法訪問protected成員,子類則能夠訪問。

另外一個問題是,類的使用很不方便,使用new創建對象,卻使用destory函數釋放對象,而不是使用delete。(使用delete會報錯,由於delete對象的指針,會調用對象的析構函數,而析構函數類外不可訪問)這種使用方式比較怪異。爲了統一,能夠將構造函數設爲protected,而後提供一個public的static函數來完成構造,這樣不使用new,而是使用一個函數來構造,使用一個函數來析構。代碼以下,相似於單例模式:

class A  
{  
protected:  
    A(){}  
    ~A(){}  
public:  
    static A* create()  
    {  
        return new A();  
    }  
    void destory()  
    {  
        delete this;  
    }  
};  

這樣,調用create()函數在堆上建立類A對象,調用destory()函數釋放內存。

只能創建在棧上(重載new函數設爲私有)

只有使用new運算符,對象纔會創建在堆上,所以,只要禁用new運算符就能夠實現類對象只能創建在棧上。將operator new()設爲私有便可。代碼以下:

class A  
{  
private:  
    void* operator new(size_t t){}     // 注意函數的第一個參數和返回值都是固定的  
    void operator delete(void* ptr){} // 重載了new就須要重載delete  
public:  
    A(){}  
    ~A(){}  
};
相關文章
相關標籤/搜索