條款8 了解各種不同意義的new和delete

2021-07-25 07:03:04 字數 4038 閱讀 7271

new operator,這個操作符是由語言內建的,不能被改變意義,總是做相同的事情。它的動作分為兩方面:

第一,它分配足夠的記憶體,用來放置某型別的物件。

第二,它呼叫乙個constructor,為剛才分配的記憶體中的那個物件設定初值。

你能夠改變的是用來容納物件的那塊記憶體的分配行為。

new operator呼叫某個函式,執行必要的記憶體分配動作,你可以重寫或過載那個函式,改變其行為。這個函式的名稱叫做operator new。

函式operator new通常宣告如下:

void * operator new(size_t size);

其返回值型別是void*。此函式返回乙個指標,指向一塊原始的、未設初值的記憶體。函式中的size_t引數表示需要分配多少記憶體,你可以將operator new過載,加上額外的引數,但第一引數的型別必須總是size_t。

你可以像呼叫任何其他函式一樣地呼叫它:

void *rawmemory=operator new(sizeof(string));

這裡的operator new將返回指標,指向一塊足夠容納乙個string物件的記憶體。

和malloc一樣,operator new的唯一任務就是分配記憶體。取得operator new返回的記憶體並將之轉換為乙個物件,是new operator的責任。當你的編譯器看到這樣乙個句子:

string *ps=new

string("memory management");

它必須產生一些**,或多或少會反映以下行為:

void *memory=operator

new(sizeof(string)); //取得原始記憶體(raw memory)用來放置乙個string物件

call string::string("memory management") on *memory; //將記憶體中的物件初始化

string *ps=static_cast

(memory); //讓ps指向新完成的物件

placement new(特殊版本的operator new)

下面示範如何使用placement new:

class widget

wdiget * constructwidgetinbuffer(void *buffer,int widgetsize)

對於表示式new (buffer) widget(widgetsize)

這是new operator的用法之一,其中指定乙個額外自變數(buffer)作為new operator「隱式呼叫operator new」時所用。於是,被呼叫的operator new除了接受「一定得有的size_t自變數」之外,還接受了乙個void*引數,指向一塊記憶體,準備用來接受構造好的物件。這樣的operator new就是所謂的placement new,看起來像這樣:

void * operator

new(size_t, void *location)

看起來很簡單,畢竟operator new的目的是要為物件找到一塊記憶體,然後返回乙個指標指向它。在placement new的情況下,呼叫者已經知道指向記憶體的指標了,因為呼叫者知道物件應該放在**。因此placement new唯一需要做的就是將它獲得的指標再返回。至於沒有用到(但一定得有)的size_t引數,之所以不賦予名稱,為的是避免編譯器發出「某物未被使用」的警告。placement new是c++標準程式庫的一部分。欲使用placement new,你必須用#include 。

如果你希望將物件產生於heap,請使用new operator。它不但分配記憶體而且為該物件呼叫乙個constructor。

如果你只是打算分配記憶體,請呼叫operator new,那就沒有任何constructor會被呼叫。

如果你打算在heap objects產生時自己決定記憶體分配方式,請寫乙個自己的operator new,並使用new operator,它將會自動呼叫你所寫的operator new。

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

刪除與記憶體釋放

為了避免資源洩漏,每乙個動態分配行為都必須匹配乙個相應但相反的釋放動作。函式operator delete對於內建的delete operator,就好像operator new對於new operator一樣。

當你寫出這樣的**:

string *ps;

...delete ps;

它必須既能夠析構ps所指物件,又能釋放被該物件占用的記憶體。

記憶體釋放動作是由函式operator delete執行,通常宣告如下:

void

operator

delete(void *memorytobedeallocated)

因此,下面這個動作:delete ps;

會造成編譯器產生近似這樣的**:

ps->~string();

operator

delete(ps);

這裡呈現的乙個暗示就是,如果你只是打算處理原始的、未設初值的記憶體,應該完全迴避new operator和delete operator,改呼叫operator new取得記憶體並以operator delete歸還給系統。

這組行為在c++中相當於呼叫malloc和free。

如果你使用placement new,在某記憶體塊中產生物件,你應該避免對那塊記憶體使用delete operator。因為delete operator會呼叫operator delete來釋放記憶體,但是該記憶體內含的物件最初並非是由operator new分配得來的。畢竟placement new只是返回它所接受的指標而已,誰知道那個指標從**來呢?所以為了抵消該物件的constructor的影響,你應該直接呼叫該物件的destructor:

//以下函式用來分配及釋放shared memory中的記憶體

void

* mallocshared(size_t size);

void freeshared(void

*memory);

void

*sharedmemory=mallocshared(sizeof(widget));

widget *pw=constructwidgetinbuffer(sharedmemory,10);

...delete pw; //無定義!

//因為sharedmemory來自mallocshared,不是來自operator new

pw->~widget(); //可以!析構pw所指的widget物件,

//但並未釋放widget占用的記憶體。

freeshared(pw); //可以!釋放pw所指的記憶體,不呼叫任何destructor

如此例所示,如果交給placement new的原始記憶體本身是動態分配而得,那麼你最終還是得釋放那塊記憶體,以免遭受記憶體洩漏之苦。

陣列

string *ps=new

string[10]; //分配乙個物件陣列

由於誕生的是陣列,所以new operator的行為與先前產生單一物件的情況略有不同,由乙個名為operator new的函式負責分配。和operator new一樣,operator new也可以被過載。

「陣列版」與「單一物件版」的new operator的第二個不同是,它所呼叫的constructor數量。陣列版new operator必須針對陣列中的每個物件呼叫乙個constructor。

同樣道理,當delete operator被用於陣列,它會針對陣列中的每個元素呼叫其destructor,然後再呼叫operator delete釋放記憶體。

就好像你可以取代或過載operator delete一樣,你也可以取代或過載operator delete。不過兩者的過載有著相同的限制。

M8 了解各種不同意義的new和delete

1 首先考慮new operator,new operator 可以認為做了三件事情 a 呼叫operator new分配一塊記憶體 b 在這塊記憶體上呼叫構造方法構造物件 返回指標。2 operator new的宣告如下 void operator new size t size 和c中的mall...

條款49 了解new handle行為

多執行緒下的記憶體管理與單執行緒下是完全不同的,因為heap是乙個可以被全域性改動的資源,所以所有的執行緒都有可能去訪問這一資源,這回導致很多的race conditions。當operator new未取得想要的記憶體的時候,會呼叫乙個使用者指定的處理函式,new handler。這個函式可以使用...

條款42 了解typename的雙重意義

條款42 了解typename的雙重意義 includeusing namespace std templatevoid print2nd const c container templatevoid f const c container,不允許使用typename c並不是巢狀 從屬型別名稱,解...