瞭解new-handle的行爲
當operator new拋出異常以反映一個未獲滿足的內存需求之前,它會先調用一個客戶指定的錯誤處理函數,一個所謂的new_handle。(new真正做的事情稍微更復雜些)爲了指定該函數,客戶必須要調用set_new_handler,那是聲明於< new >的一個標準程序庫函數:
namespace std{
typedef void(* new_handler)();//new_handler是一個typedef,定義一個指針指向函數,該函數沒有參數也不返回任何東西。
new_handler set_new_handler(new_handler p) throw();//set_new_handler則是獲得一個new_handler並返回一個new_handler的函數。參數指針指向operator new 無法分配足夠內存時該被調用的函數,返回的指針指向set_new_handler被調用前正在執行的那個new_handler函數。
}
void MyOutOfMemory()
{
cout << "Out of memory error!" << endl;
abort();
}
int main()
{
set_new_handler(MyOutOfMemory);
int *verybigmemory = new int[0x1fffffff];
...
}
定義於< new> 中的nothrow常量–該常量用來作爲operator new和operator new[]的參數,說明這兩個函數分配內存失敗時不拋出異常,而是返回一個null指針。
class widget{...};
widget* pw1 = new widget;//如果分配失敗,拋出bad_alloc
if(pw1 == 0 )..//該測試一定失敗
widget* pw2 = new (std::nothrow) widget;//如果分配失敗,返回0
if(pw2 == 0 )...//該測試可能成功
PS:< new >頭文件有:
Functions
operator new
operator new[]
operator delete
operator delete[]
set_new_handler
get_new_handler
Types(類型)
nothrow_t
new_handler
bad_alloc
bad_array_new_length
Constants(常量)
- nothrow
編譯器自帶的new和delete PK 自制版本的new和delete
自制版本的new和delete的好處:
- 用來檢測運用上的錯誤
- 效能勝過缺省版本
- 自制版本的new和delete可輕鬆收集下列信息:在定製性new和定製性delete之前,理當先收集你的軟件如何使用其動態內存,分配區塊的大小分佈如何?壽命分佈如何?傾向於FIFO(先進先出)還是LIFO次序或隨機次序來分配和歸還?運用型態是否隨時間改變,也就是在不同的執行階段有不同的分配/歸還型態嗎?任何時刻所使用的最大動態分配量是多少?
齊位(alignment):
許多計算機體系結構要求特定的類型必須放在特定的內存地址上,例如它可能會要求指針的地址必須是4倍數或doubles的地址必須是8倍數。如果沒有奉行這個約束條件,可能導致運行期硬件異常。
C++要求所有operator new返回的指針都有適當的對齊(取決於數據類型)。
編寫new和delete需要遵守的規則
new
operator new 的返回值,如果有能力供應客戶申請的內存,就返回一個指針指向那塊內存。如果沒有那個能力,就調用new_handle函數,該函數也許能做某些動作將某些內存釋放出來,只有當指向該函數的指針是null,operator new纔會拋出一個bad_alloc異常。
operator new成員函數會被derived class繼承,但是,寫定製型內存管理器的一個最常見理由是爲針對某特定class對象分配行爲提供最優化,卻不是爲該class的任何derived class。也就是說,針對class X設計的operator new,其行爲很典型地只爲大小剛好爲sizeof(X)而設計。處理該問題的最佳做法就是:改採用標準operator new,例如
void* Base::operator new(std::size_t size) throw(std::bad_alloc) { if(size != sizeof(Base)) return ::operator new(size);//採用標準operator new ... }
delete
- 保證”刪除null指針”永遠安全
- 如果class專屬的operator new將大小有誤的分配行爲轉交::operator new 執行,則必須將大小有誤的刪除行爲轉交::operator delete執行。
void* Base::operator delete(void* rawmemory,std::size_t size) throw() { if(rawmemory== 0 ) return;//保證"刪除null指針"永遠安全 if(size != sizeof(Base)){ ::operator delete(rawmemory);//採用標準operator delete return; } 歸還rawmemory所指的內存 return; }
placement new和placement delete
C++在全局作用域內提供以下三種operator new:
throwing | void* operator new (std::size_t size); |
nothrow | void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) noexcept; |
placement | void* operator new (std::size_t size, void* ptr) noexcept; |
和以下三種operator delete:
ordinary | void operator delete (void* ptr) noexcept; |
nothrow | void operator delete (void* ptr, const std::nothrow_t& nothrow_constant) noexcept; |
placement | void operator delete (void* ptr, void* voidptr2) noexcept; |
PS:
- C++11中的noexcept大致等同於C++98版的throw();都表示函數不拋出任何異常。
- operator new詳細介紹
- operator delete詳細介紹
當widget* pw = new widget
時,共有兩個函數被調用:一個是用以分配內存的operator new,一個是widget的default構造函數。
假設第一個函數調用成功,第二個函數拋出異常,則運行期系統有責任尋找並調用operator new的相應operator delete版本。
需注意的是,如果找不到與之對應的delete,運行期系統會什麼都不做,導致內存泄漏。
因此,若定製new和delete請成對,一個帶額外參數的operator new最好有帶相同額外參數的對應版operator delete。例如:
class widget{
public:
//placement new
static void* operator new(std::size_t size, std::ostream& logstream) throw(std::bad_alloc);
//ordinary delete
static void* operator delete(void* pmemory) throw();
//placement delete
static void* operator delete(void* pmemory,std::ostream& logstream) throw();
};
對所有與placement new相關的內存泄漏,我們必須同時提供一個正常的operator delete(用於構造期間無任何異常被拋出)和一個帶相同額外參數的placement版本(用於構造期間有異常被拋出)。