一、new 操作符(new operator)
人們有時好像喜歡有意使C++語言的術語難以理解。比方說new操作符(new operator)和operator new的差別。
當你寫這種代碼:
string *ps = new string("Memory Management");
你使用的new是new操作符。
這個操作符就象sizeof一樣是語言內置的。你不能改變它的含義,它的功能總是一樣的。它要完畢的功能分成兩部分。第一部分是分配足夠的內存以便容納所需類型的對象。
第二部分是它調用構造函數初始化內存中的對象。new操作符總是做這兩件事情,你不能以不論什么方式改變它的行為。
(總結就是,new操作符做兩件事,分配內存+調用構造函數初始化。你不能改變它的行為。)
二、operator new
你所能改變的是怎樣為對象分配內存。
new操作符調用一個函數來完畢必需的內存分配,你可以重寫或重載這個函數來改變它的行為。new操作符為分配內存所調用函數的名字是operator new。
函數operator new 通常這樣聲明:
void * operator new(size_t size);
返回值類型是void*,由于這個函數返回一個未經處理(raw)的指針。未初始化的內存。(假設你喜歡。你能寫一種operator new函數,在返回一個指針之前可以初始化內存以存儲一些數值,可是一般不這么做。)參數size_t確定分配多少內存。
你能添加額外的參數重載函數operator new,可是第一個參數類型必須是size_t。如: new(__FILE__, __LINE__)
(有關operator new很多其它的信息參見Effective C++ 條款8至條款10。)
你一般不會直接調用operator new,可是一旦這么做。你能夠象調用其他函數一樣調用它:
void *rawMemory = operator new(sizeof(string));
操作符operator new將返回一個指針,指向一塊足夠容納一個string類型對象的內存。
就象malloc一樣,operator new的職責僅僅是分配內存。
它對構造函數一無所知。operator new所了解的是內存分配。把operator new 返回的未經處理的指針傳遞給一個對象是new操作符的工作。當你的編譯器遇見這種語句:
string *ps = new string("Memory Management");
它生成的代碼或多或少與以下的代碼相似(很多其它的細節見Effective C++條款8和條款10。還有我的文章Counting object里的凝視。):
void *memory = operator new(sizeof(string)); // 得到未經處理的內存,為String對象
call string::string("Memory Management") on *memory; // 內存中的對象
string *ps = static_cast<string*>(memory); // 使ps指針指向新的對象
注意第二步包括了構造函數的調用,你做為一個程序猿被禁止這樣去做。你的編譯器則沒有這個約束,它能夠做它想做的一切。
因此假設你想建立一個堆對象就必須用new操作符。不能直接調用構造函數來初始化對象。(總結:operator new是用來分配內存的函數,為new操作符調用。能夠被重載(有限制))
三、placement new
有時你確實想直接調用構造函數。在一個已存在的對象上調用構造函數是沒有意義的,由于構造函數用來初始化對象。而一個對象只能在給它初值時被初始化一次。
可是有時你有一些已經被分配可是尚未處理的的(raw)內存,你須要在這些內存中構造一個對象。你能夠使用一個特殊的operator new ,它被稱為placement new。
以下的樣例是placement new怎樣使用,考慮一下:
class Widget {public:Widget(int widgetSize);...
};Widget * constructWidgetInBuffer(void *buffer,int widgetSize)
{return new (buffer) Widget(widgetSize);
}
這個函數返回一個指針。指向一個Widget對象,對象在轉遞給函數的buffer里分配。
當程序使用共享內存或memory-mapped I/O時這個函數可能實用,由于在這樣程序里對象必須被放置在一個確定地址上或一塊被例程分配的內存里。(參見條款4,一個怎樣使用placement new的一個不同樣例。)
在constructWidgetInBuffer里面。返回的表達式是: new (buffer) Widget(widgetSize)
這初看上去有些陌生,可是它是new操作符的一個使用方法,須要使用一個額外的變量(buffer)。當new操作符隱含調用operator new函數時。把這個變量傳遞給它。被調用的operator new函數除了帶有強制的參數size_t外,還必須接受void*指針參數。指向構造對象占用的內存空間。這個operator new就是placement new,它看上去象這樣:
void * operator new(size_t, void *location)
{return location;
}
這可能比你期望的要簡單,可是這就是placement new須要做的事情。畢竟operator new的目的是為對象分配內存然后返回指向該內存的指針。在使用placement new的情況下,調用者已經獲得了指向內存的指針。由于調用者知道對象應該放在哪里。placement new必須做的就是返回轉遞給它的指針。(沒實用的(可是強制的)參數size_t沒有名字,以防止編譯器發出警告說它沒有被使用。見條款6。
) placement new是標準C++庫的一部分。為了使用placement new。你必須使用語句#include <new>
(或者假設你的編譯器還不支持這新風格的頭文件名稱)。
(總結:placement new是一種特殊的operator new,作用于一塊已分配但未處理或未初始化的raw內存)
四、小結
讓我們從placement new回來片刻,看看new操作符(new operator)與operator new的關系,(new操作符調用operator new)
- 你想在堆上建立一個對象,應該用new操作符。它既分配內存又為對象調用構造函數。
- 假設你只想分配內存,就應該調用operator new函數;它不會調用構造函數。
- 假設你想定制自己的在堆對象被建立時的內存分配過程,你應該寫你自己的operator new函數。然后使用new操作符,new操作符會調用你定制的operator new。
- 假設你想在一塊已經獲得指針的內存里建立一個對象。應該用placement new。
五、Deletion and Memory Deallocation
為了避免內存泄漏,每一個動態內存分配必須與一個等同相反的deallocation相應。
1.函數operator delete與delete操作符的關系與operator new與new操作符的關系一樣。當你看到這些代碼:
string *ps;
...
delete ps; // 使用delete 操作符
你的編譯器會生成代碼來析構對象并釋放對象占有的內存。
Operator delete用來釋放內存。它被這樣聲明:
void operator delete(void *memoryToBeDeallocated);
因此, delete ps; 導致編譯器生成類似于這種代碼:
ps->~string(); // call the object's dtor
operator delete(ps); // deallocate the memory the object occupied
這有一個隱含的意思是假設你僅僅想處理未被初始化的內存,你應該繞過new和delete操作符,而調用operator new 獲得內存和operator delete釋放內存給系統:
void *buffer = operator new(50*sizeof(char)); // 分配足夠的內存以容納50個char//沒有調用構造函數...
operator delete(buffer); // 釋放內存// 沒有調用析構函數
這與在C中調用malloc和free等同。
2.placement new建立的對象怎樣釋放?
假設你用placement new在內存中建立對象,你應該避免在該內存中用delete操作符。
由于delete操作符調用operator delete來釋放內存,可是包括對象的內存最初不是被operator new分配的。placement new僅僅是返回轉遞給它的指針。誰知道這個指針來自何方?而你應該顯式調用對象的析構函數來解除構造函數的影響:
// 在共享內存中分配和釋放內存的函數 void * mallocShared(size_t size);void freeShared(void *memory);
void *sharedMemory = mallocShared(sizeof(Widget));
Widget *pw = // 如上所看到的,
constructWidgetInBuffer(sharedMemory, 10); // 使用// placement new ...
delete pw; // 結果不確定! 共享內存來自
// mallocShared, 而不是operator newpw->~Widget(); // 正確。 析構 pw指向的Widget,// 可是沒有釋放
//包括Widget的內存freeShared(pw); // 正確。 釋放pw指向的共享內存// 可是沒有調用析構函數
如上例所看到的,假設傳遞給placement new的raw內存是自己動態分配的(通過一些不經常使用的方法),假設你希望避免內存泄漏,你必須釋放它。(參見我的文章Counting objects里面關于placement delete的凝視。)
六、數組
到眼下為止一切順利。可是還得接著走。
到眼下為止我們所測試的都是一次建立一個對象。
如何分配數組?會發生什么?
string *ps = new string[10]; // allocate an array of objects
被使用的new仍然是new操作符,可是建立數組時new操作符的行為與單個對象建立有少許不同。
第一是內存不再用operator new分配,取代以等同的數組分配函數,叫做operator new[]
(常常被稱為array new)。
它與operator new一樣能被重載。
這就同意你控制數組的內存分配。就象你能控制單個對象內存分配一樣(可是有一些限制性說明,參見Effective C++ 條款8)。
(operator new[]對于C++來說是一個比較新的東西。所以你的編譯器可能不支持它。假設它不支持。不管在數組中的對象類型是什么。全局operator new將被用來給每一個數組分配內存。
在這種編譯器下定制數組內存分配是困難的。由于它須要重寫全局operator new。這可不是一個能輕易接受的任務。
缺省情況下,全局operator new處理程序中全部的動態內存分配,所以它行為的不論什么改變都將有深入和普遍的影響。并且全局operator new有一個正常的簽名(normal signature)(也就單一的參數size_t。參見Effective C++條款9)。所以假設你 決定用自己的方法聲明它,你立馬使你的程序與其他庫不兼容基于這些考慮,在缺乏operator new[]支持的編譯器里為數組定制內存管理不是一個合理的設計。)
第二個不同是new操作符調用構造函數的數量。對于數組,在數組里的每個對象的構造函數都必須被調用:
string *ps = new string[10]; // 調用operator new[]為10個string對象分配內存,// 然后對每一個數組元素調用string對象的缺省構造函數。
相同當delete操作符用于數組時,它為每一個數組元素調用析構函數,然后調用operator delete來釋放內存。(buxizhizhou530注:這里應該是operator delete[]吧)
就象你能替換或重載operator delete一樣,你也替換或重載operator delete[]。
在它們重載的方法上有一些限制。
請參考優秀的C++教材。
(總結:數組時,兩個不同點,一時調用operator new[]函數,二是new操作符調用構造函數的數量不同。)
七、總結
new和delete操作符是內置的,其行為不受你的控制。凡是它們調用的內存分配和釋放函數則能夠控制。當你想定制new和delete操作符的行為時,請記住你不能真的做到這一點。你僅僅能改變它們為完畢它們的功能所採取的方法,而它們所完畢的功能則被語言固定下來。不能改變。(You can modify how they do what they do, but what they do is fixed by the language)
參考:C++中delete, new以及new [], delete[]操作符內幕
本文大部分內容來源于上述博文,可惜這個博文也是轉載,沒有找到原出處。本文僅是依據上述博文內容,加上自己理解,進行了又一次排版、顏色標注、相關改動、及自己的總結。
假設錯誤。歡迎交流~