轉載自:
http://dev.yesky.com/242/2585242.shtml
人們有時好像喜歡故意使C++語言的術語難以理解。比如說new操作符(new operator)和operator new的區別。
當你寫這樣的代碼:
| string *ps = new string("Memory Management"); |
你使用的new是new操作符。這個操作符就象sizeof一樣是語言內建的,你不能改變它的含義,它的功能總是一樣的。它要完成的功能分成兩部分。第一部分是分配足夠的記憶體以便容納所需類型的對象。第二部分是它調用建構函式初始化記憶體中的對象。new操作符總是做這兩件事情,你不能以任何方式改變它的行為。
你所能改變的是如何為對象分配記憶體。new操作符調用一個函數來完成必需的記憶體配置,你能夠重寫或重載這個函數來改變它的行為。new操作符為分配記憶體所調用函數的名字是operator new。
函數operator new 通常這樣聲明:
| void * operator new(size_t size); |
傳回值類型是void*,因為這個函數返回一個未經處理(raw)的指標,未初始化的記憶體。(如果你喜歡,你能寫一種operator new函數,在返回一個指標之前能夠初始化記憶體以儲存一些數值,但是一般不這麼做。)參數size_t確定分配多少記憶體。你能增加額外的參數重載函數operator new,但是第一個參數類型必須是size_t。(有關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 = // 是ps指標指向 static_cast<string*>(memory); // 新的對象 |
注意第二步包含了建構函式的調用,你做為一個程式員被禁止這樣去做。你的編譯器則沒有這個約束,它可以做它想做的一切。因此如果你想建立一個堆對象就必須用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++庫的一部分(見Effective C++ 條款49)。為了使用placement
new,你必須使用語句#include <new>(或者如果你的編譯器還不支援這新風格的標頭檔名(再參見Effective C++ 條款49),<new.h>)。
讓我們從placement new回來片刻,看看new操作符(new operator)與operator new的關係,你想在堆上建立一個對象,應該用new操作符。它既分配記憶體又為對象調用建構函式。如果你僅僅想分配記憶體,就應該調用operator new函數;它不會調用建構函式。如果你想定製自己的在堆對象被建立時的記憶體配置過程,你應該寫你自己的operator new函數,然後使用new操作符,new操作符會調用你定製的operator new。如果你想在一塊已經獲得指標的記憶體裡建立一個對象,應該用placement
new。
(有關更多的不同的new與delete的觀點參見Effective C++ 條款7和我的文章Counting objects。)
Deletion and Memory Deallocation
為了避免記憶體流失,每個動態記憶體分配必須與一個等同相反的deallocation對應。函數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等同。
如果你用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; // 結果不確定! 共用記憶體來自