定制new和delete
第49條:了解new_handler的行為
什么是new_handler?
- new_handler是一個函數指針類型,定義在
頭文件中,它為函數std::set_new_handler()與std::get_new_handler()所使用. 原型定義如下所示:
typedef void (*new_handler)()
使用new_handler定義了一個全局new的處理函數指針對象,例如 new_handler g_new_handle_func = nullptr. 設計該函數的作用是:當new操作符進行內存分配失敗時會調用該函數, 希望它可以做下面三件事之一:
- 該函數可以通過某些操作,獲取更多可用的內存,從而使下一次new操作可能成功。
- 該函數可以選擇終止程序, 因為內存不夠使用了,再執行下去可能沒有意義了。
- 該函數可以選擇拋出 std::bad_alloc 的異常。
- std::set_new_handler()函數定義在
頭文件中, 它的作用是設置新的全局new的處理函數並返回先前安裝的new處理函數。 - std::get_new_handler()函數定義在
頭文件中,它的作用是返回當前安裝的new的處理函數,它有可能返回空指針的,表示沒有處理函數。
new_handler的處理過程
- 當new操作符申請內存時,如果發現沒有足夠多的內存可以使用時,它會去檢查new_handler處理函數是否為nullptr, 如果為空,則拋出異常;當不為空時,它會調用該處理函數。
- 如果new處理函數拋出異常或終止程序時,程序就會終止掉了。
- 如果new處理函數正常返回(例如它可以嘗試通過一系列的努力從其它地方找出來一些內存),new操作符就會又繼續重復一開始的內存申請操作了。
特別說明:如果你自定義的new處理函數沒有做上面說的三件事之一,當內存不足時,你的代碼就會進行死循環了.
不合理的new處理函數說明:
#include <iostream>
#include <new>
using namespace std;
void my_new_handle()
{
cout << "現在死循環中...\n" << endl;
}
int main()
{
new_handler old_handle = set_new_handler(my_new_handle);
unsigned int size = 1 << 31;
long *p = new long[size];
return 0;
}
第50條:為什么有時需要替換c++原有的new與delete操作符呢?
- 通過重載new/delete操作符可以用於檢測一些關於內存操作的錯誤信息,例如可以實現內存泄漏的小工具。
- 可以加強專屬的new/delete的性能。標准庫提供的new/delete具體通用性,對於某些場景可以定義自己的new/delete運算符。
第51條:重載new與delete運行符時的注意事項
- operator new內應該含有一個無窮循環,在循環內進行內存分配,如果內存不足時就應該調用new_handler處理函數。
- c++規定安申請0字符的內存時也應該返回一個合法的指針,因此operator new操作符應該處理這種情況。
- 重載operator delete操作符時,記住一點:c++保證刪除NULL指針永遠是安全的。 所以你要處理這種特殊情況。
第52條:placement new 與 palcement delete版本
如果operator new 接受的參數除了一定會有的size_t之外,還有其它的參數,則認為這就是所謂的 placement new. 通常使用的placement new 是一個接受一個指針指向對象該被構造之處,表示從指針所指的地方進行內存申請。 這樣版本的new已經納入了c++標准程序庫里了,它的格式如下所示:
void* operator new(std::size_t, void* pMemory) throw();
當然,也可以定義其它形式的placement new, 例如下面所示:
void* operator new(std::size_t size, const string& info) {
cout << info;
return ::operator new(size);
}
我們知道,當我們寫一個new表達式時,共有兩個函數被調用:一個是用於內存分配的operator new, 另一個是對象的構造函數。如果在第一步,內存申請成功了,然后在執行對象的構造函數時被拋出了異常,如果不處理申請到的內存就會造成內存泄漏問題。 編譯器會這么做:它會查找到與oeprator new函數的參數相同的oeprator delete的函數,然后調用該operator delete進行內存的釋放,如果沒有找到對應的函數編譯器什么也不做。 因此,如果你想自定義placement new函數的話,你也需要定義一個對應的placement delete函數,該函數只會在new一個對象時如果內存申請成功但是構造失敗的時候調用,用於防止內存泄漏, 正常情況下是不會被調用的。舉例說明:
#include <iostream>
#include <new>
#include <exception>
using namespace std;
void* operator new(std::size_t size, int)
{
std::cout << "placement new is called. " << std::endl;
return ::operator new(size);
}
void operator delete (void* ptr)
{
std::cout << "regular operator delete is called." << std::endl;
}
void operator delete(void* ptr, int)
{
std::cout << "placement delete is called. " << std::endl;
}
class Obj {
public:
Obj() { throw std::bad_alloc(); };
};
class NoThrowObj {
public:
NoThrowObj() {};
};
int main()
{
try {
// 在new Obj時,它的構造函數拋出異常,並且會調用placement delete 進行內存的釋放。
Obj* p = new (100) Obj;
} catch (...) {
std::cout << "已經捕獲。" << std::endl;
}
// 在進行delete時,常規的operator delte 被調用。
NoThrowObj* p = new (1000) NoThrowObj;
delete p;
return 0;
}
輸出為:
placement new is called.
placement delete is called.
已經捕獲。
placement new is called.
regular operator delete is called.
