effective c++ 08 ——定製 new/delete
阿新 • • 發佈:2021-01-06
技術標籤:C++
東陽的學習筆記
- 多執行緒環境下的記憶體管理遭受單執行緒系統不曾有過的挑戰。
- STL所使用的heap記憶體是由容器所擁有的分配器物件(allocator objects)管理
條款49:瞭解new-handler的行為
- 當operator new 丟擲異常以反映一個未獲滿足的記憶體需求之前,它會先呼叫一個客戶指定的錯誤處理函式,一個所謂的 new-handler。為了指定這個 “用以處理記憶體不足的”函式,客戶必須呼叫
set_new_handler
,其聲明於標頭檔案 。
namespace std {
typedef void (*new_handler) ( );
new_handler set_new_handler(new_handler p) throw();
}
- set_new_handler 的引數是個指標,指向 operator 無法分配足夠記憶體時該被呼叫的函式。其返回值也是個指標。
void outOfMem()
{
std::cerr << "Unable to satisfy request for memory\n";
std::abort();
}
int main()
{
std::set_new_handler(outOfMem); // 當 new 無法分配足夠記憶體時呼叫
int *pBigDataArray = new int[10000000000L];
std::cout << "i am alive" << std::endl;
return 0;
}
- 一個設計良好的 new-handler 必須做以下事情:
i. 讓更多的記憶體可被使用。實現此策略的一個辦法是:程式一開始就分配一大塊記憶體(提前佔用),當new_handler 第一次被呼叫時,將他們釋放返還給程式使用。
ii. 安裝另一個 new_handler。為達此目的,做法之一就是讓當前 new_handler 修改“會影響new_handler行為” 的static資料、namespace資料、global資料等。
iv. 丟擲 bad_alloc,將異常傳播到記憶體索求處
v. 不返回,通常呼叫abort() / exit() - DO IT FOR ME:
class Widget: public NewHandlerSupport<Widget> { ... }
- 舊版本的C++,分配失敗就返回
null
:(使用std::nothrow
宣告使用返回null的版本)
Widget *pw2 = new ( std::nothrow ) Widget; // 如果分配失敗,則返回0
if (pw2 == 0) ... // 偵測 null
- Nothrow new 是一個頗為侷限的工具,因為它只適用於記憶體分配;後面的
建構函式呼叫還是可能丟擲異常
條款50:瞭解 new / delete 的合理替換時機
- 什麼時候需要替換編譯器提供的 new / delete 呢?以下是三個常見的理由:
i. 用來檢測運用上的錯誤。比如我們自行定義一個 new,以額外空間(位於客戶所得區塊之前或後)放置特定的 byte pattern (即簽名,signatures)。delete 通過檢查簽名是否原封不動來判斷是否發生了overrun
或underrun
,並可志記(log)下來。
ii. 為了強化效能。編譯器自帶的 new / delete 採取中庸之道
。不對特定任何人有最佳表現。通過定製版本
,是獲得最大效能提升的方法之一。
iii. 為了收集使用上的統計資料。瞭解程式對記憶體的使用習慣。 - 編寫自己的 new / delete時注意
齊位
- operator new 都應該包含一個迴圈,反覆呼叫某個
new_handling 函式
。 - 何時替換預設的 new / delete:
i. 為了增加分配和歸還的速度
ii. 為了降低預設記憶體管理器帶來的額外空間開銷
iii. 為了彌補預設記憶體管理器中的非最佳齊位
iv. 為了將相關物件成簇集中(在更少的記憶體頁上)
v. 為了獲得非傳統的行為
vi. 為了檢測運用錯誤
vii. 為了收集使用統計資訊
條款51:編寫 new / delete時需固守常規
- new 不只一次嘗試分配記憶體,並在每次失敗後呼叫
new-handling
函式 - operator new 應該包含一個無限迴圈,退出迴圈的唯一方法是:
記憶體分配成功
或丟擲異常
- 如果你打算控制class專屬之 array 控制行為,那麼你需要實現 operator new []。記住:為一要做的事就是分配一塊未加工記憶體(
row memory
)。因為你無法知道每個物件有多大(在即成體系中, base / derived 的大小不一樣) - delete 更簡單:你需要記住的唯一事情就是C++保證刪除 null 指標永遠安全。
如果刪除的是個null指標,則什麼也不做
void operator delete(void *rawMemory) throw()
{
if (rawMemory == 0) return; // 如果刪除的是個null指標,則什麼也不做
}
- member版本的 delete,只需多一個動作檢查刪除數量。萬一你的 class 專屬的 operator new 將大小有誤的分配行為轉交給 ::operator new執行,你也必須將刪除行為轉交給 ::operator delete執行。
條款52:寫了placement new 也要寫 placement delete
Widget *pw = new Widget;
- 上述語句共呼叫了兩個函式:new 和 建構函式
- 假設第一個函式呼叫成功,第二個函式卻丟擲異常。這樣將會導致 pw 未被成功賦值,客戶手上也沒有能夠指標指向該被歸還的記憶體。
取消步驟一併恢復舊觀的責任因此落到C++執行期系統上
- 執行期系統會高高興興地呼叫步驟一呼叫的 new 的相應 delete 版本。
void* operator new(std::size_t) throw(std::bad_alloc);
void operator delete(void* rawMemory) throw();//global 作用域中的正常簽名式
void operator delete(void* rawMemory, std::size_t size) throw();//class作用域中的典型簽名式。
- 當你自定義自己的 new 時, 也就是帶了附加引數的 new 時,究竟哪一個 delete 應該被呼叫就是一個問題了
class Widget{
public:
static void* operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc);
static void* operator delete(void* pMemory, std::size_t size) throw();
};
- 如上例,一個有額外引數的 new 卻定義了一個 正常的 delete。此時C++執行期系統無法正常 delete,因此需要定義一個與
自定義new
對應的placement delete
。此時,對應的placement delete
會被呼叫,丟擲異常,但是,此時卻並不會洩漏任何記憶體
class Widget{
public:
static void* operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc);
static void* operator delete(void* pMemory, std::size_t size) throw();
static void* operator delete(void* pMemory, std::ostream& logStream) throw();
};
placement delete
只有在 “伴隨placement new
呼叫而觸發的建構函式”出現異常時才會被呼叫,對一個指標施行 delete絕對不對呼叫
placement delete
- 由於成員函式的名稱會
外圍作用域
中的相同名稱,因此:
i. 建立一個 base class 內含所有正常形式的 new / delete
ii. 繼承上述類,並使用 using 宣告
class StandardNewDeleteForms{
public:
//normal new/delete
static void* operator new(std::size_t size)throw(std::bad_alloc)
{return ::operator new(size);}
static void operator delete(void* pMemory) throw()
{::operator delete(pMemory);}
//placement new/delete
static void* operator new(std::size_t size, void* ptr) throw()
{return ::operator new(size, ptr);}
static void operator delete(void* pMemory, void* ptr)throw()
{return ::operator delete(pMemory, ptr);}
//nothrow new/delete
static void* operator new(std::size_t size, const std::nothrow_t& nt)throw()
{return ::operator new(size, nt);}
static void operator delete(void* pMemory, const std::nothrow_t&) throw()
{::operator delete(pMemory);}
};
class Widget: public StandardNewDeleteForms{
public:
using StandardNewDeleteForms::operator new;
using StandardNewDeleteForms::operator delete;
static void* operator new(std::size_t size, std::ostream& logStream)throw(std::bad_alloc);
static void operator delete(void* pMemory, std::ostream& logStream) throw();
};
當你宣告 placement new / delete,請確定不要無意識(非故意)地遮掩了他們的正常版本