C++中的new和delete

來源:互聯網
上載者:User

1. new與operator new

C++中有很多文法讓人難以理解,如:new operator(操作符,下同)和operator new之間差異,確切的說,應該是new與operator new 的區別。

1.1 new operator

如下代碼:

string *ps=new string("memory management");

這裡所使用的new就是所謂new operator,是由C++語言內建的,就像sizeof那樣,不能改變意義,總是做相同的事情。

這個動作的含義分為兩方面:

第一,它分配足夠的記憶體,用來放置某類型的對象。對於上例而言,它分配足夠放置一個string 對象記憶體。

第二,它調用一個建構函式,為剛才分配的記憶體中的那個對象設定初始值。

new operator總是做這兩件事,無論如何你是不能改變其行為。

1.2 operator new

能夠改變的是用來容納對象的那塊記憶體的分配行為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的責任。

1.3 當編譯器看到這個句子:

string *ps=new string("memory management");

它必須產生一些代碼,或多或少會反映如下行為:

1) void* memory=operator new(sizeof(string));   //取得原始記憶體,用於放置一個string對象

2) call string::string("memory management") on *memory;//將記憶體中對象初始化

3) string *ps=static_cast<string*>(memory);   //讓ps指向新完成的對象

注意第二步,調用一個建構函式。身為程式員沒有權利繞過new operator像這麼使用建構函式,但是編譯器卻是這麼乾的

這就是為什麼如果你想要做出一個heap-based object,一定要用new operator的原因。

也就是說new 出來的東西都放在heap裡面,而無法直接調用“對象初始化所必須的建構函式”。 

2. placement new

2.1 有時候你真的會想直接調用一個建構函式,針對一個已經存在的對象調用其建構函式,並無意義,因為建構函式用來對象初始化,而對象只能初始化一次。但是你偶爾會有一些分配好的原始記憶體,你需要在上面構建對象,有一個特殊的地方 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映射。這類函數可能是有用的,因為在那樣運用中,對象必須置於特定的地址,或者置於特殊函數分配出來的記憶體上。

 2.2 函數內部

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之間的關係。

兩個術語表面上令人迷惑,但其實很好理解:

1)如果你希望將對象產生於heap,就是得new operator,它不但分配記憶體而為該對象調用一個建構函式。

2)如果你只是打算分配記憶體,請用operator new,就沒有建構函式被調用。

3)如果你打算在heap object產生自己決定的記憶體配置方式,請寫一個自己的operator new。並使用new operator,它將會自動調用你所寫的operator new。

4)如果你打算在已經分配(並擁有指標)的記憶體構造對象,請使用placement new 。

 3. delete 與記憶體釋放

為了避免resource leaks,每一個動態分配行為都必須匹配一個相應的釋放動作。

3.1 函數 operator delete對於內建的delete operator(操作符)就好像 operator new 對於new operator一樣。

string *ps;

...

delete ps; //使用delete operator.

記憶體釋放動作是由operator delete執行的,通常聲明如下:

void operator delete(void* memoryToBeDeallocated);

因此 delete ps;會造成編譯器代碼如下:

1)ps->~string();//調用解構函式

2)operator delete(ps);//釋放對象所佔用的記憶體

3.2 這裡提示我們,如果只打算處理原始的、未設初值的記憶體,應該完全迴避 new operator和delete operator。改為調用operator new取得記憶體並以operator delete歸還系統。

例如:

void* buffer=operator new (50*sizeof(char));//分配記憶體,放置50個char,沒有調用建構函式

...

operator delete(buffer); //釋放記憶體,而沒有直接調用解構函式。

這組行為類似malloc和free。

3.3 placement new

如果使用了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。 

4. 動態分配數組(Arrays)

前面所做的都是基於單一對象上的,如果是一組對象呢?

string *ps=new string[10];//分配一個對象數組

4.1 這裡的new 與前面的new 行為類似,但略有不同,這裡不能再operator new分配記憶體,而是以operator new[]負責分配

和operator new 一樣,operator new[]也可以被重載。

註:operator new[]是相當晚的時候才加入C++的一個特性,所以你的編譯器不一定能支援它。如果是這樣,全域的operator new 會被用來為每一個數組分配記憶體(不論數組中的對象是什麼類型)。在這樣的編譯器下定製“數組記憶體配置行為”很困難,因為你得改寫全域的operator new才行。預設情況下,全域版的operator new 負責程式中所有的動態記憶體分配,所以其行為的任何改變都可能帶來全域的影響。

另外,前面講過,operator new 只允許size_t一個參數。所以你如果決定聲明為自己的函數,你的程式便不相容於任何做了相同決定的程式庫。

多方面考慮之下,如果編譯器不支援operator new[],定製數組記憶體管理行為,不是一個明智的決定。

4.2 數組的new 與單一對象的new所調用的建構函式不同,數組的new 必須針對數組中每一個對象調用一個建構函式。

string *ps=new string[10];//調用operator new[]以分配足夠容納10個string對象的記憶體,然後針對每個元素調用string的預設建構函式。

同樣的,當使用了delete,它也會針對數組中每一個元素調用解構函式,然後再調用operator delete[]釋放記憶體。

如:delete []ps;//為數組中的每一個元素調用string 解構函式,然後再調用 operator delete[] 釋放記憶體。 (先調用解構函式,再釋放記憶體。)

跟operator delete一樣 operator delete[]也可以被重載。

 

最後小結一下,new 和delete都是內建的操作符,語言本身所固定了,無法重新定製。但它所調用的記憶體配置/釋放的函數,即operator new和operator delete可以被重載。

 

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.