C++ 中 new 操做符內幕:new operator、operator new、placement new

1、new 操做符(new operator)

人們有時好像喜歡有益使C++語言的術語難以理解。比方說new操做符(new operator)和operator new的差異。 html

當你寫這種代碼:
數組

string *ps = new string("Memory Management");
你使用的new是 new操做符

這個操做符就象sizeof同樣是語言內置的。你不能改變它的含義,它的功能老是同樣的。它要完畢的功能分紅兩部分。第一部分是分配足夠的內存以便容納所需類型的對象。app

第二部分是它調用構造函數初始化內存中的對象。new操做符老是作這兩件事情,你不能以不論什麼方式改變它的行爲。
(總結就是,new操做符作兩件事,分配內存+調用構造函數初始化。函數

你不能改變它的行爲。)

post

2、operator new

你所能改變的是怎樣爲對象分配內存spa

new操做符調用一個函數來完畢必需的內存分配,你能夠重寫或重載這個函數來改變它的行爲。new操做符爲分配內存所調用函數的名字operator new
.net

函數operator new 一般這樣聲明:
設計

void * operator new(size_t size);
返回值類型是void*,因爲這個函數返回一個未經處理(raw)的指針。未初始化的內存。(假設你喜歡。你能寫一種operator new函數,在返回一個指針以前能夠初始化內存以存儲一些數值,但是通常不這麼作。

)參數size_t肯定分配多少內存。指針

你能添加額外的參數重載函數operator new,但是第一個參數類型必須是size_t。code

(有關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裏的凝視。):
<pre name="code" class="cpp">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操做符調用。可以被重載(有限制)

3、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內存

4、小結

讓咱們從placement new回來片刻,看看new操做符(new operator)與operator new的關係,(new操做符調用operator new)

  • 你想在堆上創建一個對象,應該用new操做符。它既分配內存又爲對象調用構造函數。
  • 假設你只想分配內存,就應該調用operator new函數;它不會調用構造函數。
  • 假設你想定製本身的在堆對象被創建時的內存分配過程,你應該寫你本身的operator new函數。而後使用new操做符,new操做符會調用你定製的operator new。
  • 假設你想在一塊已經得到指針的內存裏創建一個對象。應該用placement new。

5、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等同。


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 new

pw->~Widget(); // 正確。 析構 pw指向的Widget,

// 但是沒有釋放
//包括Widget的內存

freeShared(pw); // 正確。 釋放pw指向的共享內存

// 但是沒有調用析構函數
如上例所看到的,假設傳遞給placement new的raw內存是本身動態分配的(經過一些不常用的方法),假設你但願避免內存泄漏,你必須釋放它。(參見個人文章Counting objects裏面關於placement delete的凝視。)

6、數組

到眼下爲止一切順利。但是還得接着走。

到眼下爲止咱們所測試的都是一次創建一個對象。

如何分配數組?會發生什麼?

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操做符調用構造函數的數量不一樣。)

7、總結

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[]操做符內幕

本文大部份內容來源於上述博文,惋惜這個博文也是轉載,沒有找到原出處。本文僅是依據上述博文內容,加上本身理解,進行了又一次排版、顏色標註、相關改動、及本身的總結。

假設錯誤。歡迎交流~

相關:

C++中的new、operator new與placement new  有額外補充

C++中placement new操做符(經典)  通常。可略看

相關文章
相關標籤/搜索