new和delete在C++中特別要重,在此簡單總結一下new和delete各種含義。
new與operator new
C++中有很多語法讓人難以理解,如:new operator(操作符,下同)和operator new之間差異,
確切的說,應該是new與operator new 的區別。
如下代碼:
string *ps=new string("memory management");
這裡所使用的new就是所謂new operator,是由C++語言內建的,就像sizeof那樣,不能改變意義,總是做相同的事情。
這個動作的含義分為兩方面:
第一,它分配足夠的內存,用來放置某類型的對象。對於上例而言,它分配足夠放置一個string 對象內存。
第二,它調用一個構造函數,為剛才分配的內存中的那個對象設定初始值。
new operator總是做這兩件事,無論如何你是不能改變其行為。
能夠改變的是用來容納對象的那塊內存的分配行為,new operator調用某個函數,執行必要的內存分配動作,你可以重寫或者重載那個函數,改變其行為。這個函數名稱就叫operator new 。開始暈了吧?
函數 operator new 通常聲明如下:
void * operator new (size_t size);
其返回類型void*。即返回一個指針,指向一塊原始的、未設置初始值的內存。
函數中的size_t參數表示需要分配多少內存,你可以將operator new 重載,加上額外的參數,但第一個參數類型必須總是size_t。
或者你從來沒有直接用過operator new .但是你可以調用任何其他函數一樣地調用它。
void* rawMemory=operator new(sizeof(string));
這裡的operator new 將返回指針,它指幾一塊足夠容納string對象的內存。
和malloc一樣,operator new 的唯一任務就是分配內存,它不知道什麼是構造函數,它只負責分配內存。
取得operator new 返回的內存並將之轉為一個對象,是new operator的責任。
當編譯器看到這個句子:
string *ps=new string("memory management");
它必須產生一些代碼,或多或少會反映如下行為:
void* memory=operator new(sizeof(string)); //取得原始內存,用於放置一個string對象
call string::string("memory management") on *memory;//將內存中對象初始化
string *ps=static_cast<string*>(memory); //讓ps指向新完成的對象
注意第二步,調用一個構造函數。身為程序員沒有權利繞過new operator像這麼使用構造函數,但是編譯器卻是這麼干的。
這就是為什麼如果你想要做出一個heap-based object,一定要用new operator的原因。
也就是說new 出來的東西都放在heap裡面,而無法直接調用“對象初始化所必須的構造函數”。
placement new
有時候你真的會想直接調用一個構造函數,針對一個已經存在的對象調用其構造函數,並無意義,因為構造函數用來對象初始化,而對象只能只能初始化一次。但是你偶爾會有一些分配好的原始內存,你需要在上面構建對象,有一個特殊的地方 operator new 稱為placement new,允許這麼做。
例如:
class Widget
{
public:
Widget(int widgetSize);
......
};
Widget* constructWidgetInBuffer(void *buffer,int size)
{
return new (buffer) Widget(size);
}
此函數返回指針,指向一個Widget object,它被構造於傳遞給此函數的一塊內存緩存區上。當程序運行到共享內存或者內存I/O映射。這類函數可能是有用的,因為在那樣運用中,對象必須置於特定的地址,或者置於特殊函數分配出來的內存上。
函數內部
Widget* constructWidgetInBuffer 只有一個表達式new (buffer) Widget(size),
有點奇怪,其實不為奇,new operator的用法之一,其中所指定一個額外的自變量(buffer)作為new operator "隱式調用operator new "時所用。於是,被調用的operator new 除了接受"一定要有size_t自變量"之外,還接受了一個void* 參數,指向一塊內存,准備用來接受構造好的對象。這樣的operator new 就是所謂的placement new 類似:
void * operator new(size_t size,void* location)
{
return location;
}
operator new 的目的是要為對象找到一塊內存,然後返回一個指針指向它,在placement new 的情況下,調用者已經知道指向內存的指針了,因為調用者知道對象應該放在哪裡。因此placement new 唯一需要做的就是將它獲得的指針再返回。
至於沒有用到(但一定得有)的size_t參數,之所以不賦予名稱,為的是避免"編譯器某物未被使用"的警告。
另外注:placement new 是C++標准程序庫的一部分,要使用placement new 得用#include<new>,舊式編譯器用 #include<new.h>
回頭想想placement new ,我們便能了解new operator和operator new之間的關系。
兩個術語表面上令人迷惑,但其實很好理解:
如果你希望將對象產生於heap,就是得new operator,它不但分配內存而為該對象調用一個構造函數。、
如果你只是打算分配內存,請用operator new,就沒有構造函數被調用。
如果你打算在heap object產生自己決定的內存分配方式,請寫一個自己的operator new。並使用new operator,它將會自動調用你所寫的operator new。
如果你打算在已經分配(並擁有指針)的內存構造對象,請使用placement new 。
delete 與內存釋放
為了避免resource leaks,每一個動態分配行為都必須匹配一個相應的釋放動作。
函數 operator delete對於內建的delete operator(操作符)就好像 operator new 對於new operator一樣。
string *ps;
...
delete ps; //使用delete operator.
內存釋放動作是由operator delete執行的,通常聲明如下:
void operator delete(void* memoryToBeDeallocated);
因此 delete ps;
會造成編譯器代碼如下:
ps->~string();//調用析造函數
operator delete(ps);//釋放對象所占用的內存
這裡提示我們,如果只打算處理原始的、未設初值的內存,應該完全回避 new operator和delete operator。
改為調用operator new取得內存並以operator delete歸還系統。
如:
void* buffer=operator new (50*sizeof(char));//分配內存,放置50個char,沒有調用構造函數
...
operator delete(buffer); //釋放內存,而沒有直接調用析構函數。
這組行為類似malloc和free。
如果使用了placement new ,在某塊內存中產生對象,你應該避免那塊內存使用delete operator(操作符)。
因為delete operator會調用operator delete來釋放內存,但是該內存所含的對象最初並不是由operator new 分配來的。
placement new只是返回它接收的指針而已,誰知道那個指針從哪裡來呢?
所以為了抵消該對象的構造函數的影響,使用placement new 時應該直接調用該對象的析構函數。
請看示例:
void * mallocShared(size_t size);//申請分配內存
void freeShared(void * momery);//釋放內存
void* sharedMemory=mallocShared(sizeof(Widget));
Widget *pw=constructWidgetBuffer(sharedMemory,10);//使用前面Widget類的placement new
...
delete pw;//無定義,因為sharedMemory來自mallocShared,不是來自new
pw->~Widget();//OK,析構函數pw所指Widget對象,但並釋放Widget所占用內存。
freeShared(pw);//OK,釋放pw所指的內存,不調用任何析構函數。
如上述所示,如果交給placement new的原始內存(raw memory)本身是動態分配而得的,那麼最終得釋放那塊內存,以避免memory leak
更詳細分析請參見《Counting Objects in C++》
數組(Arrays)
前面所做的都是基於單一對象上的,如果是一組對象呢?
另外,前面講過,operator new 只允許size_t一個參數。所以你如果決定聲明為自己的函數,你的程序便不兼容於任何做了相同決定的程序庫。
多方面考慮之下,如果編譯器不支持operator new[],定制數組內存管理行為,不是一個明智的決定。
2.數組的new 與單一對象的new所調用的構造函數不同,數組的new 必須針對數組中每一個對象調用一個構造函數。
string *ps=new string[10];//調用operator new[]以分配足夠容納10個string對象的內存,然後針對每個元素調用string的默認構造函數。
同樣的,當使用了delete,它