new是C++程式設計語言中的一種語言結構,用於動態分配記憶體、並用建構函式初始化分配的記憶體。
new的使用稱為“new運算子運算式”,其內部實現分為兩步:
調用相應的operator new()函數,動態分配記憶體。如果operator new()不能成功獲得記憶體,則調用new_handler函數。如果沒有設定new_handler函數或者new_handler未能分配足夠記憶體,則拋出std::bad_alloc異常。“new運算子運算式”所調用的operator new()函數,按照C++的名字尋找規則,首先做依賴於實參的名字尋找(即ADL規則),在要申請記憶體的資料類型T的內部、資料類型T定義處的命名空間尋找;如果沒有尋找到,則直接調用全域的::operator new()函數。
在分配到的動態記憶體塊上初始化相應類型的對象並返回其首地址。如果調用建構函式初始化對象時拋出異常,則自動調用operator delete()函數釋放已經分配到的記憶體。
每個new擷取的對象,必須用delete析構並釋放記憶體,以免記憶體流失。
new運算子運算式是C++的一種語言結構,不可重載。但使用者可重載operator new()函數。
2.new運算子運算式
2.1 普通的new運算子
model:
p = new typename;
p = new typename(init);
p = new typename{init};
```
eg:
```c++
int *p = new int;
int *p = new int(5);
int *p = new int{5};//C++ 11
2.2 產生對象數組的new運算子
model:
p = new type[size];
p = new type[size] {one, two , size};
eg:
p = new int[5];
p = new int[5]{1, 2, 3, 4, 5};
2.3 帶位置的new運算子
model:
new ( expression-list ) type ( init);
expression-list將作為 operator new() 函數的實參列表的結尾部分。這種形式的new運算子運算式首先調用 operator new(size_t,OtherTypeList) 函數來擷取記憶體;然後對該對象執行建構函式。這裡的OtherTypeList作為形參列表要和new括弧裡的實參列表expression-list的類型相容(即形參實參能夠匹配)。
帶位置的new運算子,語義上包括四種使用情形:
直接給出要構建的對象的記憶體位置;
不拋出異常,如果記憶體配置失敗返回null 指標;
定製的、帶其他參數的記憶體 Clerk;
用於調試目的,在建構函式調用失敗時給出源檔案名稱與行號。
狹義上的帶位置的new是指第一種情形。使用這種placement new, 原因之一 是使用者的程式不能在一塊記憶體上自行調用其建構函式(即使用者的程式不能顯式調用建構函式),必須由編譯系統產生的程式碼調用建構函式。 原因之二 是可能需要把對象放在特定硬體的記憶體位址上,或者放在多處理器核心的共用的記憶體位址上。
釋放這種對象時,不能調用placement delete,應直接調用解構函式,如:pObj->~ClassType();然後再自行釋放記憶體。
#include <iostream>
using namespace std;
int main(int argc, char* argv[]) {
char buf[100];
int *p=new (buf) int(101);
cout<<*(int*)buf<<endl;
return 0;
}
2.4 保證不拋出異常的new 運算子
在分配記憶體失敗時,new運算子的標準行為是拋出std::bad_alloc異常。也可以讓new運算子在分配記憶體失敗時不拋出異常而是返回null 指標。其中 nothrow 是 std::nothrow_t 的一個執行個體.
p = new (nothrow) type(init);
p = new (nothrow) type[size];
2.5 自行定製參數的new運算子運算式
new運算子的參數可以是任意合法類型的列表。由C++的重載機制來決定調用那個operator new。
new (Type_list) Type ( init );
new (Type_list) Type[size];
2.6 帶位置的delete運算子
C++ 不能使用帶位置的 delete 運算子運算式直接析構一個對象但不釋放其記憶體。因此,對於用廣義的帶位置new運算式構建的對象,析構釋放時有兩種辦法:
第一種辦法是直接寫一個函數,完成析構對象、釋放記憶體的操作 。
3.operator new() 函數重載
3.1普通的operator new(size_t size)函數
operator new函數可以被每個C++類作為成員函數重載。也可以作為全域函數重載。
void * operator new (std::size_t) throw(std::bad_alloc);
void operator delete(void*) throw();
記憶體需要回收的話,調用對應的operator delete()函數。
例如,在new運算子運算式的第二步,調用建構函式初始化記憶體時如果拋出異常,異常處理機制在棧展開(stack unwinding)時,要回收在new運算子運算式的第一步已經動態分配到的記憶體,這時就會自動調用對應operator delete()函數。
3.2數組形式的operator new [](size_t size)函數
void * operator new[] (std::size_t) throw(std::bad_alloc);
void operator delete[](void*) throw();
3.3 帶位置的operator new函數void operator new(size_t,void )
operator new(size_t,void*) 函數用於帶位置的new運算子調用。C++標準庫已經提供了operator new(size_t,void*)函數的實現,包含 <new> 標頭檔即可。這個實現只是簡單的把參數的指定的地址返回,帶位置的new運算子就會在該地址上調用建構函式來初始化對象。
// Default placement versions of operator new.
inline void* operator new(std::size_t, void* __p) throw() { return __p; }
inline void* operator new[](std::size_t, void* __p) throw() { return __p; }
// Default placement versions of operator delete.
inline void operator delete (void*, void*) throw() { }
inline void operator delete[](void*, void*) throw() { }
禁止重定義這4個函數。因為都已經作為 <new> 的內嵌函式了。在使用時,實際上不需要 #include <new>
對應的placement delete函數,只應在placement new運算子運算式在第二步調用建構函式拋出異常時被異常處理機制的棧展開操作自動調用。
3.5保證不拋出異常的operator new函數
C++標準庫的 <new> 中還提供了一個nothrow的實現,使用者可寫自己的函數替代:
void* operator new(std::size_t, const std::nothrow_t&) throw();
void* operator new[](std::size_t, const std::nothrow_t&) throw();
void operator delete(void*, const std::nothrow_t&) throw();
void operator delete[](void*, const std::nothrow_t&) throw();
3.6自行定製參數的operator new函數
這種函數被自行定製參數的new算符調用。需要由使用者自行定義,以確定分配記憶體時的行為.
operator new(size_,Type1, Type2, ... );
eg:
char data[1000][sizeof(int)];
inline void* operator new(size_t ,int n) ;{
return data[n];
}
void foo(){
int *p=new(6) int(102); //把整型對象建立在data的第六個單元上
}
placement new 放置new
>void*operator new(std::size_t ,void *);
void operator delete( void * ,void *); 該運算子是在已指派的記憶體上重新構造對象,因為不分配記憶體,所以不必擔心分配失敗。唯一的工作是調用建構函式。要包含 <new>標頭檔。
# include <new>
# include <iostream>
void main()
{ using namespace std;
char * p = new(nothrow) char [4];
if (p == NULL)
{ cout < <“allocte failed” < <endl; exit( -1 ); }
// ...
long * q = new(p)long(1000);
delete [ ]p; //只釋放 p,不要用q釋放。
} p和q僅僅是首址相同,所構建的對象可以類型不同。所“放置”的空間應小於原空間,以防不測。當”放置new”超過了申請的範圍,Debug版下會掛機,但Release版竟然能運行而不出錯!
該運算子的作用是:只要第一次分配成功,不再擔心分配失敗。
# include <new>
# include <iostream>
void main()
{ using namespace std;
char * p = new(nothrow) char [100];
if (p == NULL)
{ cout < <“allocte failed” < <endl; exit( -1 ); }
long * q1 = new(p)long(100);
// 使用q1 ...
int * q2 = new(p) int[100/sizeof(int) ];
// 使用q2 ...
ADT * q3 = new(p) ADT[100/sizeof(ADT) ];
// 使用q3 然後釋放對象 ...
delete [ ]p; //只釋放空間,不再析構對象。
}注意:使用該運算子構造的對象或數組,一定要顯式調用解構函式,不可用delete代替析構,因為placement new 的對象的大小不再與原空間相同。
# include <new>
# include <iostream>
void main()
{ using namespace std;
char * p = new(nothrow) char [sizeof(ADT)+2];
if (p == NULL)
{ cout < <“allocte failed” < <endl; exit( -1 ); }
// ...
ADT * q = new(p) ADT;
// ...
// delete q; // 錯誤
q-> ADT::~ADT(); //顯式調用解構函式,僅釋放對象
delete [ ]p; //最後,再用原指標來釋放記憶體.
} placement new 的主要用途就是可以反覆使用一塊已申請成功的記憶體空間。這樣可以避免申請失敗的徒勞,又可以避免使用後的釋放。
特別要注意的是對於 placement new 絕不可以調用的delete, 因為該new只是使用別人替它申請的地方(只是個租房戶,不是房主。無權將房子賣掉)。釋放記憶體是nothrow new的事,即要使用原來的指標釋放記憶體