條款50:了解new和delete的合理替換時機
Understand when it makes sense to replace new and delete.
怎麼會有人想要替換編譯器提供的operator new或operator delete呢?我們可以列出如下三個常見的理由:
■ 用來檢測運用上的錯誤.
程式員從開始編寫代碼到調試直至最終完成,這一過程當中犯下各種各樣的錯誤在所難免,這些錯誤就可能
導緻記憶體洩露(memory leaks)、不确定行為産生、overruns(寫入點在配置設定區塊尾端之後)、underruns(寫入點
在配置設定區塊尾端之前)等不良結果的發生.如果我們自定義operator news,我們就可以超額配置設定記憶體,用額外空
間放置特定簽名來檢測類問題.
■ 為了強化效能.
我們所用的編譯器中自帶的operator new和operator delete主要是用于一般的目的能夠為各種類型的程式
所接受,而不考慮特定的程式類型.它們必須處理一系列需求,必須接納各種配置設定形态,必須要考慮破碎問題等等
這些問題,是以編譯器所帶的operator new和operator delete采取中庸之道也是沒辦法的事情.它們的工作對每
個人都是适度地好,但不對特定任何人有最佳表現.通常可以發現,定制版之operator new和operator delete性
能勝過預設版本.所謂的'勝過',就是它們比較快,有時甚至快很多,而且它們需要記憶體比較少,最高可省50%,所
以說對某些運用程式而言,将預設new和delete替換為定制版本,是獲得重大效能提升的辦法之一.
■ 為了收集使用上的統計資料.
收集你的軟體如何使用其動态記憶體.配置設定區塊的大小釋出如何?壽命釋出如何?它們傾向于以FIFO次序或LIFO
次序或随機次序來配置設定和歸還?它們的運用形态是否随時間改變,也就是說你的軟體在不同執行階段有不同的配置設定
/歸還形态嗎?任何時刻所使用的最大動态記憶體配置設定量是多少?自行定義的operator new和operator delete使我們
得以輕松收集這些資訊.
基于上述三種理由,我不得不開始了寫一個定制型operator new了,我的初稿看起來如下述代碼所示,其中還存
在不少小錯誤,稍後我會完善它.
typedef const int signature = 0xDEADBEEF;
typedef unsigned char Byte;
void* operator new( std::size_t size)throw(std::bad_alloc)
{
using namespace std;
size_t real_size = size + 2 * sizeof(int);
void* applied_memory = malloc( real_size );
if( applied_memory == 0 ){
throw bad_alloc();
}
//将signature寫入記憶體的最前段落和最後段落.
*(static_cast<int*>( applied_memory ) = signature;
*(reinterpret_cast<int*>(static_cast<Byte*>( applied_memory )
+ real_size - sizeof(int) ) ) = signature;
//傳回指針,指向恰位于第一個signature之後的記憶體位置.
return static_cast<Byte*>( applied_memory ) + sizeof(int);
}
我剛才說了,這個版本有不少問題.而現在我隻想專注一個比較微妙的主題:齊位(alignment).關于齊位的具
體是什麼? 我假設大家都已經知道了,我在這裡就不唠叨講了.因為C++要求所有operator news傳回的指針都有
适當的對齊(取決于資料類型).malloc就是在這樣的要求下工作的,是以令operator new傳回一個得自malloc的指
針是安全地.然而上述operator new中我并未傳回一個得自malloc的指針,而是傳回一個得自malloc且偏移一個
int大小的指針.沒有人能夠保證它的安全!我們可能因使用該版本的operator new導緻獲得一個未有适當齊位的
指針.那可能會造成程式崩潰或執行速度變慢.不論那種情況都不是我們希望看到的結果.
寫一個總是能夠運作的記憶體管理器并不難,難的是它能夠優良地運作.一般而言,本書的作者建議你在必要的
稍後才試着寫寫看.很多時候這是非必要的.某些編譯器已經在它們的記憶體管理函數中切換至調試狀态和志記狀态
.快速浏覽一下你的編譯器文檔,很可能就消除了你自行寫new和delete的需要了.
另一個選擇是開放源碼領域中的記憶體管理器.它們對許多平台都可用,你可以下載下傳試試.Boost程式庫的Pool就
是這樣一個配置設定器,它對于常見的'配置設定大量小型對象'很有幫助.TR1支援各種類型特定對齊條件,很值得注意.
讨論到這裡,我們又可以為本款開頭讨論的問題理由再添加幾條了,呵呵:
■ 為了增加配置設定和歸還的速度.
泛用型配置設定器往往比定制型配置設定器慢,特别是當定制型配置設定器專門針對某特定類型之對象而設計時.
■ 為減低預設記憶體管理器帶來的空間額外開銷.
泛用型記憶體管理器往往還使用更多的記憶體,那是因為它們往往常常在每一個配置設定區塊身上招引某些額外開銷.
■ 為了彌補預設配置設定器中的非最佳齊位.
■ 為了将相關對象成簇集中(詳略).
■ 為了獲得非傳統行為(詳略).
OK,our topic talk is over!
請記住:
■ 有許多理由需要寫個自定義的new和delete,包括改善效能,對heap運用錯誤進行調試,收集heap使用資訊.