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