參考:C++ 內存分配(new,operator new)詳解
new運算符和operator new()
new:指我們在C++里通常用到的運算符,比如A* a = new A; 對於new來說,有new和::new之分,前者位於std
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運算符中完成的
new和operator new之間的關系
(1)new :不能被重載,其行為總是一致的。它先調用operator new分配內存,然后調用構造函數初始化那段內存。
new 操作符的執行過程:
1. 調用operator new分配內存 ;
2. 調用構造函數生成類對象;
3. 返回相應指針。
(2)operator new:要實現不同的內存分配行為,應該重載operator new,而不是new。
operator new就像operator + 一樣,是可以重載的。如果類中沒有重載operator new,那么調用的就是全局的::operator new來完成堆的分配。同理,operator new[]、operator delete、operator delete[]也是可以重載的。
如何限制對象只能建立在堆上或者棧上
在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(){} };