做眾籌網(wǎng)站成都十大營銷策劃公司
有時候我們覺得,C++的術(shù)語仿佛是要故意讓人難以理解似的。
這里就有一個例子:請說明new operator 和operator new 之間的差異(譯注:本書所說的new operator,即某些C++教程如C++ Primer 所謂的new expression)
當(dāng)你寫出這樣的代碼:
string *ps= new string("Memory Management");
你所使用的 new 是所謂的new operator。
這個操作符是由語言內(nèi)建的,就像sizeof那樣,不能被改變意義,總是做相同的事情。
它的動作分為兩方面。
- 第一,它分配足夠的內(nèi)存,用來放督某類型的對象。以上例而言,它分配足夠放置一個string 對象的內(nèi)存。
- 第二,它調(diào)用一個constructor,為剛才分配的內(nèi)存中的那個對象設(shè)定初值。
new operator總是做這兩件事,無論如何你不能夠改變其行為。
你能夠改變的是用來容納對象的那塊內(nèi)存的分配行為。也就是上面的第一步
new operator 調(diào)用某個函數(shù),執(zhí)行必要的內(nèi)存分配動作,你可以重寫或重載那個函數(shù),改變其行為。這個函數(shù)的名稱叫做operator new。頭昏了嗎?真的,我說的是真的。
函數(shù)operator new 通常聲明如下:
void * operator new(size_t size);
其返回值類型是void*。此函數(shù)返回一個指針,指向一塊原始的、未設(shè)初值的內(nèi)存(如果你喜歡,可以寫一個新版的operator new,在其返回內(nèi)存指針之前先將那塊內(nèi)存設(shè)定初值。只不過這種行為頗為罕見就是了)。
函數(shù)中的size_t 參數(shù)表示需要分配多少內(nèi)存。
你可以將operator new 重載,加上額外的參數(shù),但第一參數(shù)的類型必須總是 size_t(如何撰寫 operator new,相關(guān)信息請參考條款E8~E10)
吸中協(xié)從不想到要直接調(diào)用operator new,但如果你要,你可以像調(diào)用任何其他函數(shù)一樣地調(diào)用它:
void *rawMemory = operator new(sizeof(string));
這里的operator new將返回指針,指向一塊足夠容納一個string對象的內(nèi)存。
和malloc一樣,operator new的唯一任務(wù)就是分配內(nèi)存。它不知道什么是constructors, operator new只負(fù)責(zé)內(nèi)存分配。
取得 operator new 返回的內(nèi)存并將之轉(zhuǎn)換為一個對象,是new operator 的責(zé)任。
當(dāng)你的編譯器看到這樣一個句子:
string *ps= new string("Memory Management");
它必須產(chǎn)生一些代碼,或多或少會反映以下行為(見條款E8和條款E10,以及發(fā)表于C/C++ Users Journal, April1998 的文章《Counting Objects inC++》中的方塊內(nèi)容)
void *memory?=
operator new(sizeof(string));//取得原始內(nèi)存(raw memory)。用來放置一個string對象。call string::string("Memory Management")//將內(nèi)存中的對象初始化。
on *memorystring *ps=
static_cast<string*>(memory);//讓ps指向新完成的對象。
注意上述第二步驟涉及“調(diào)用一個constructor”,身為程序員的你沒有權(quán)力這么做。
然而你的編譯器百無禁忌,可以為所欲為。這就是為什么如果你想要做出一個heap-based object,一定得使用new operator 的原因:你無法直接調(diào)用“對象初始化所必需的constructor”
(尤其它可能得為重要成分vtbl設(shè)定初值,見條款24)。
Placement new
有時候你真的會想直接調(diào)用一個constructor。
針對一個已存在的對象調(diào)用其constructor 并無意義,因為 constructors 用來將對象初始化,而對象只能被初始化一次。
但是偶爾你會有一些分配好的原始內(nèi)存,你需要在上面構(gòu)建對象。有一個特殊版本的operator new,稱為placement new,允許你那么做。
下面示范如何使用 placement new:
class Widget {
public:
widget(int widgetsize);
//...
}widget * constructWidgetInBuffer (void *buffer, int widgetSize)
{
return new (buffer) Widget(widgetsize);
}
此函數(shù)返回指針,指向一個widget object,它被構(gòu)造于傳遞給此函數(shù)的一塊內(nèi)存緩沖區(qū)上。
當(dāng)程序運(yùn)行到shared memory 或memory-mapped I/O,這類函數(shù)可能是有用的,因為在那樣的運(yùn)用中,對象必須置于特定地址,或是置于以特殊函數(shù)分配出來的內(nèi)存上(條款4列有placement new的另一個運(yùn)用實例)
在 constructWidgetInBuffer 函數(shù)內(nèi)部,唯一一個表達(dá)式是,
new (buffer) Widget(widgetSize)
乍見之下有點(diǎn)奇怪,其實不足為奇,這只是new operator 的用法之一,其中指定一個額外自變量(buffer)作為 new operator “隱式調(diào)用operator new”時所用。
于是,被調(diào)用的operator new除了接受“一定得有的size_t 自變量”之外,還接受了一個void*參數(shù),指向一塊內(nèi)存,準(zhǔn)備用來接受構(gòu)造好的對象。這樣的operator new 就是所謂的placement new,看起來像這樣:
void * operator new (size_t,void *location)//注意size_t后面沒名字
{
return location;
}
似乎比你預(yù)期得更簡單,但這便是placement new必須做的一切。
畢竟operator new的目的是要為對象找到一塊內(nèi)存,然后返回一個指針指向它。
在placement new的情況下,調(diào)用者已經(jīng)知道指向內(nèi)存的指針了,因為調(diào)用者知道對象應(yīng)該放在哪里。因此placement new 唯一需要做的就是將它獲得的指針再返回。
至于沒有用到(但一定得有)的size_t參數(shù),之所以不賦予名稱,為的是避免編譯器發(fā)出“某物未被使用”的警告(見條款6)Placement new 是C++標(biāo)準(zhǔn)程序庫(見條款E49)的一部分。
欲使用 placement new,你必須用#include <new>。如果你的編譯器尚未支持新式頭文件名稱的話(見條款E49),就用#include<new.h>。
花幾分鐘回頭想想 placement new,我們便能了解 new operator 和 operator new之間的關(guān)系,兩個術(shù)語雖然表面上令人迷惑,概念上卻十分直接易懂。
- 如果你希望將對象產(chǎn)生于heap,請使用 new operator。它不但分配內(nèi)存而且為該對象調(diào)用一個constructor。
- 如果你只是打算分配內(nèi)存,請調(diào)用 operator new,那就沒有任何constructor會被調(diào)用。
- 如果你打算在heap objects 產(chǎn)生時自己決定內(nèi)存分配方式,請寫一個自己的 operator new,并使用 new operator,它將會自動調(diào)用你所寫的operator new。
- 如果你打算在已分配(并擁有指針)的內(nèi)存中構(gòu)造對象,請使用placement new(若想更深入地了解new和delete,請見條款E7及發(fā)表于C/C++Users Journal, April1998的文章《Counting Objects in C++》)
刪除(Deletion)與內(nèi)存釋放(Deallocation)
為了避免resource leaks(資源泄漏),每一個動態(tài)分配行為都必須匹配一個相應(yīng)但相反的釋放動作。函數(shù)operator delete 對于內(nèi)建的delete operator,就好像operator new對于new operator 一樣。當(dāng)你寫出這樣的代碼:
string *ps;
//。。。
delete ps;// 使用 delete operator。
你的編譯器必須產(chǎn)生怎樣的代碼?
它必須既能夠析構(gòu) ps所指對象,又能夠釋放被該對象占用的內(nèi)存。
內(nèi)存釋放動作是由函數(shù)operator delete 執(zhí)行,通常聲明如下:
void operator delete(void *memoryToBeDeallocated);
因此,下面這個動作:
delete ps;
會造成編譯器產(chǎn)生近似這樣的代碼:
ps->~string();// 調(diào)用對象的dtoroperator。operator delete (ps);//釋放對象所占用的內(nèi)存。
這里呈現(xiàn)的一個暗示就是,如果你只打算處理原始的、未設(shè)初值的內(nèi)存,應(yīng)該完全回避new operator 和 delete operators,改調(diào)用operator new取得內(nèi)存并以operator delete 歸還給系統(tǒng):
void *buffer=operator new(50*sizeof(char));//分配足夠的內(nèi)存,放置50個 chars;沒有調(diào)用任何ctorsoperator delete(buffer);//釋放內(nèi)存,沒有調(diào)用任何dtors。
這組行為在C++中相當(dāng)于調(diào)用malloc和free。
如果你使用placement new,在某內(nèi)存塊中產(chǎn)生對象,你應(yīng)該避免對那塊內(nèi)存使用delete opcrator.因為 delete operator會調(diào)用 operator delete來釋放內(nèi)存,但是該內(nèi)存內(nèi)含的對象最初并非是由 operator new分配得來的。
畢竟placemen new只是返回它所接收的指針而已,誰知道那個指針從哪里來呢?所以為了抵消該對象的constructor的影響,你應(yīng)該直接調(diào)用該對象的destructor:
//以下函數(shù)用來分配及釋放 shared memory 中的內(nèi)存。
void* mallocshared(size_t size);
void freeShared(void *memory);void*sharedMemory=mallocShared(sizeof(Widget));//和先前相同,運(yùn)用Widget*pw=constructWidgetInBuffer(sharedMemory, 10); // placement new。
//..
delete pw;//無定義!因為sharedMemory 來自mallocshared,不是來自 operator new。pw->~Widget();
//可!析構(gòu) pw 所指的widget 對象,
//但并未釋放widget占用的內(nèi)存。freeShared(pw);
//可!釋放 pw所指的內(nèi)存,
//不調(diào)用任何 destructor。
如此例所示,如果交給placement new 的原始內(nèi)存(raw memory)本身是動態(tài)分配而得(通過某種非傳統(tǒng)做法),那么你最終還是得釋放那塊內(nèi)存,以免遭受內(nèi)存泄漏(memory leak)之苦(請參考文章《Counting Objects inC++》之中的方塊內(nèi)容,其中對所謂的“placement delete”有些介紹)。
數(shù)組(Arrays)
目前為止一切都好,但我們還有更遠(yuǎn)的路要走。截至目前我們考慮的每件事情都只在單一對象身上打轉(zhuǎn)。面對數(shù)組怎么辦?下面會發(fā)生什么事情;
string *ps= new string[10);//分配一個對象數(shù)組
上述使用的new 仍然是那個new operator,但由于誕生的是數(shù)組,所以new operator的行為與先前產(chǎn)生單一對象的情況略有不同。
是的,內(nèi)存不再以operator new分配,而是由其“數(shù)組版”兄弟,一個名為operator new[]的函數(shù)負(fù)責(zé)分配(通常被稱為“aray new”)。
和operator new一樣,operator new[]也可以被重載。這使你得奪取數(shù)組的內(nèi)存分配權(quán),就像你可以控制單一對象的內(nèi)存分配一樣(不過條款E8對此有些警告)。
operatornew[]是相當(dāng)晚的時候才加入C++的一個特性,所以你的編譯器或許尚未支持它。
如果是這樣,全局operator new會被用來為每個數(shù)組分配內(nèi)存一不論數(shù)組中的對象類型是什么。
在這樣的編譯器下定制“數(shù)組內(nèi)存分配行為”很困難,因為你得改寫全局版的 operator new才行。這可不是件容易的工作。
默認(rèn)情況下全局版的operator new負(fù)責(zé)程序中所有的動態(tài)內(nèi)存分配,所以其行為的任何改變都可能帶來劇烈而普遍的影響。此外,全局版本的operator new,其正規(guī)形式的型構(gòu)(eignature)(我的意思是,只有唯一size_t參數(shù)的那個,見條款E9)只有一個,所以如果你決定聲稱它為你所擁有,你的軟件便立刻不容于任何做了相同決定的程序庫(見條款 27)。
多方考慮之下,如果你面對的是尚未支持 。perator new[]的編譯器,定制“數(shù)組內(nèi)存管理行為”往往不是個理想的決定。
“數(shù)組版”與“單一對象版”的newoperator的第二個不同是,它所調(diào)用的constructor數(shù)量。數(shù)組版new operator 必須針對數(shù)組中的每個對象調(diào)用一個constructor:
string *ps =//調(diào)用operator new[]以分配足夠容納new string[10];//10個 string 對象的內(nèi)存,然后
//針對每個元素調(diào)用 string default ctor。
同樣道理,當(dāng)delete operator 被用于數(shù)組,它會針對數(shù)組中的每個元素調(diào)用其 destructor,然后再調(diào)用 operator delete[]釋放內(nèi)存:
delete []?ps;
//為數(shù)組中的每個元素調(diào)用 string dtor.
然后調(diào)用 operator delete[]以釋放內(nèi)存。
就好像你可以取代或重載 operator delete一樣,你也可以取代或重載operator delete[]。不過兩者的重載有著相同的限制。請你找一本好的C++教程,查閱其細(xì)節(jié)。說到好的C++教程,本書p285列有我的一份推薦名單。
現(xiàn)在,你有了完整的知識。
new operator 和delete operator 都是內(nèi)建操作符,無法為你所控制,但是它們所調(diào)用的內(nèi)存分配/釋放函數(shù)則不然。
當(dāng)你想要定制new operator 和 delete operator的行為,記住,你其實無法真正辦到。你可以修改它們完成任務(wù)的方式,至于它們的任務(wù),已經(jīng)被語言規(guī)范固定死了。