Placement语法

**new** 是 C++ 程式設計語言中的一種語言結構，用於動態分配記憶體、並用建構函式初始化分配的記憶體。

new 的使用稱為「new 運算子表達式」，其內部實現分為兩步：

1. 呼叫相應的 operator new() 函式，動態分配記憶體。如果 operator new() 不能成功獲得記憶體，則呼叫 new\_handler 函式。如果沒有設定 new\_handler 函式或者 new\_handler 未能分配足夠記憶體，則丟擲 `std::bad_alloc` 異常。「new 運算子表達式」所呼叫的 operator new() 函式，按照 C++ 的名字尋找規則，首先做依賴於實參的名字尋找（即 ADL 規則），在要申請記憶體的資料類型 T 的內部、資料類型 T 定義處的命名空間尋找；如果沒有尋找到，則直接呼叫全域的::operator new () 函式。
2. 在分配到的動態記憶體塊上初始化相應類型的物件並返回其首位址。如果呼叫建構函式初始化物件時丟擲異常，則自動呼叫 operator delete () 函式釋放已經分配到的記憶體。

每個 **new** 取得的物件，必須用 delete 解構並釋放記憶體，以免記憶體流失。

new 運算子表達式是 C++ 的一種語言結構，不可多載。但使用者可多載 operator new() 函式。

## new 運算子表達式語法

### 普通的 new 運算子表達式

new 的語法是:

　
:

其中 `p_var` 是已經定義的類型為 `typename` 的指標變數。

通過 `new` 初始化物件，使用下述語法：

　
:

其中 `initializer` 是傳遞給建構函式的實參表或初值。

### 動態生成物件陣列的 new 運算子表達式

`new` 也可建立一個物件陣列，稱之為「array forms new」：

　
:

C++98 標準規定，`new` 建立的物件陣列不能被顯式初始化， 陣列所有元素被預設初始化。如果陣列基本類型沒有預設初始化，則編譯報錯。但 C++11 已經允許顯式初始化，例如：

　
:

如此生成的物件陣列，在釋放時必須呼叫 `delete [ ]` 表達式。例如

### 帶位置的 new 運算子表達式

**帶位置的 new** （placement new）的語法是：

　
:

其中，expression-list 將作為 operator new() 函式的實參列表的結尾部分。這種形式的 new 運算子表達式首先呼叫 operator new(size\_t,OtherTypeList) 函式來取得記憶體；然後對該物件執行建構函式。這裡的 OtherTypeList 作為形參列表要和 new 表達式中第一個括號里的實參列表 expression-list 的類型相容（即形參實參能夠匹配）。

帶位置的 new 運算子，語意上包括四種使用情形，其中前兩種已經在標準標頭檔 <new> 中實現了：

1. 直接給出要構建的物件的記憶體位置；
2. 不丟擲異常，如果記憶體分配失敗返回空指標；
3. 客製化的、帶其他參數的記憶體分配器；
4. 用於除錯目的，在建構函式呼叫失敗時給出原始檔名與行號。

狹義上的**帶位置的 new** 是指第一種情形。使用這種 placement new，原因之一是使用者的程式不能在一塊記憶體上自行呼叫其建構函式（即使用者的程式不能顯式呼叫建構函式），必須由編譯系統生成的代碼呼叫建構函式。原因之二是可能需要把物件放在特定硬體的記憶體位址上，或者放在多處理器核心的共享的記憶體位址上。

釋放這種物件時，不能呼叫 placement delete，應直接呼叫解構函式，如：`pObj->~ClassType();` 然後再自行釋放記憶體。

舉例：

### 保證不丟擲異常的 new 運算子表達式

在分配記憶體失敗時，new 運算子的標準行為是丟擲 `std::bad_alloc` 異常。也可以讓 new 運算子在分配記憶體失敗時不丟擲異常而是返回空指標。

　
:

或

　
:

其中 `nothrow` 是 `std::nothrow_t` 的一個實例

### 自行客製化參數的 new 運算子表達式

new 運算子的參數可以是任意合法類型的列表。由 C++ 的多載機制來決定呼叫那個 operator new。

　
:

或

　
:

### 帶位置的 delete 運算子表達式

C++ 不能使用帶位置的 delete 運算子表達式直接解構一個物件但不釋放其記憶體。因此，對於用廣義的帶位置 new 表達式構建的物件，解構釋放時有兩種辦法：

第一種辦法是直接寫一個函式，完成解構物件、釋放記憶體的操作：

如此使用：

第二種辦法是分兩步顯式呼叫解構函式與帶位置的 operator delete 函式：

帶位置的 operator delete () 函式，可以被帶位置的 new 算符表達式自動呼叫。這是在物件的建構函式丟擲異常的時候，用來釋放掉帶位置的 operator new 函式取得的記憶體。以避免記憶體泄露。

例如：

　
:

## operator new () 的函式多載

使用 new 動態生成一個物件，實際上是呼叫了 **new 運算子**表達式。該運算子首先呼叫了 operator new 函式動態分配記憶體，然後呼叫類型的建構函式初始化這塊記憶體。new 運算子是不能被多載的，但是下述各種 operator new() 函式既可以作為全域函式多載，也可以作為類別成員函式或作用域內的函式多載，即由編程者指定如何取得記憶體。

### 普通的 operator new (size\_t size) 函式

new 運算子呼叫 operator new 函式動態分配記憶體。首先尋找類內是否有 operator new 函式可供使用（即依賴於實參的名字尋找）。operator new 函式的參數是一個 size\_t 類型，指明了需要分配記憶體的規模。operator new 函式可以被每個 C++ 類別作為成員函式多載。也可以作為全域函式多載：

記憶體需要回收的話，呼叫對應的 operator delete() 函式。

例如，在 new 運算子表達式的第二步，呼叫建構函式初始化記憶體時如果丟擲異常，例外處理機制在棧展開（stack unwinding）時，要回收在 new 運算子表達式的第一步已經動態分配到的記憶體，這時就會自動呼叫對應 operator delete() 函式。

### 陣列形式的 operator new [](size\_t size) 函式

new Type[]運算子（array forms new），用來動態建立一個物件陣列。這需要呼叫陣列基本類型內部定義的 void\* operator new[ ](size\_t) 函式來分配記憶體。如果陣列基本類型沒有定義該函式，則呼叫全域的 void\* operator new[ ](size\_t) 函式來分配記憶體。

在 <new> 中聲明了 void\* operator new [](size\_t) 全域函式：

### void\* operator new(size\_t,void\*)

operator new(size\_t,void\*) 函式用於帶位置的 new 運算子呼叫。C++ 標準庫已經提供了 operator new(size\_t,void\*) 函式的實現，包含 <new> 標頭檔即可。這個實現只是簡單的把參數的指定的位址返回，帶位置的 new 運算子就會在該位址上呼叫建構函式來初始化物件：

**禁止重定義**這 4 個函式。因為都已經作為 <new> 的行內函式了。在使用時，實際上不需要 `#include <new>`

對應的 placement delete 函式，只應在 placement new 運算子表達式在第二步呼叫建構函式丟擲異常時被例外處理機制的棧展開操作自動呼叫。

### 保證不丟擲異常的 operator new 函式

C++ 標準庫的 <new> 中還提供了一個 nothrow 的實現，使用者可寫自己的函式替代：

### 自行客製化參數的 operator new 函式

這種函式被自行客製化參數的 new 算符呼叫。需要由使用者自行定義，以確定分配記憶體時的行為：

　
:

例如：

## 參見

* RAII
* 分配器 (C++)
* Delete (C++)
* C++ 例外處理
* `malloc`
* 記憶體池
* 位置語法
* 指標 (資訊學)
* 聰明指標

## 參考文獻

* IBM Documentation describing C++'s operator newArchive.is 的存檔，存檔日期 2013-01-03
* Microsoft Visual Studio operator new documentation（頁面存檔備份，存於網際網路檔案館）