“伊凡 C普”第一式-内存管理
<條款5>對應的new和delete要采用相同的形式。
這一條沒什么可說的,注意new的是不是數組就可以了。其中有兩點需要注意:
1.在寫一個包含指針數據成員,并且提供多個構造函數的類時,構造中new的形勢要相同,否則析構中的delete便"為難"了。
2.喜歡用typedef的人需要注意,最好杜絕對數組類型用typedefs。
=====================================
<條款6>析構函數里對指針成員調用delete
增加一個指針成員意味著幾乎都要進行下面的工作:
- 在每個構造函數里對指針進行初始化。對于一些構造函數,如果沒有內存要分配給指針的話,指針要被初始化為0(即空指針)。
- 刪除現有的內存,通過賦值操作符分配給指針新的內存。
- 在析構函數里刪除指針。
=====================================
<條款7>預先準備好內存不夠的情況
這一條款說的是:new可能調用失敗,你要做好準備工作。
一、與C中類似的方法-宏:
#definenew(ptr, type)?????????? /
try {(ptr) = new type; }?????? /
catch(std::bad_alloc&) { assert(0); }
此種方法缺點有2:
(1) 只是在沒定義標準宏ndebug的時候,即在調試狀態下才有效。
(2) 沒有考慮到new有各種各樣的使用方式。
二、當內存分配請求不能滿足時,調用你預先指定的一個出錯處理函數。
此方法基于一種機制:即當operator new不能滿足請求時,會在拋出異常之前調用客戶指定的一個出錯處理函數。
指定出錯處理函數時要用到set_new_handler函數,它的輸入參數是operator new分配內存失敗時要調用的出錯處理函數的指針,返回值是set_new_handler沒調用之前就已經在起作用的舊的出錯處理函數的指針(有點類似GDI中設置dc的畫筆)。下面是一個使用set_new_handler的例子。
//function to call if operator new can't allocateenough memory
void nomorememory()
{
cerr<< "unable to satisfy request for memory/n";
abort();
}
int main()
{
set_new_handler(nomorememory);
int*pbigdataarray = new int[100000000];
...... ...
}
operator new不能滿足內存分配請求時,new-handler函數不只調用一次,而是不斷重復,直至找到足夠的內存(里面有一個while(1)循環)。
一個設計得好的new-handler函數必須實現下面功能中的一種:
<1>產生更多的可用內存。這將使operator new下一次分配內存的嘗試有可能獲得成功。實施這一策略的一個方法是:在程序啟動時分配一個大的內存塊,然后在第一次調用new-handler時釋放。釋放時伴隨著一些對用戶的警告信息,如內存數量太少,下次請求可能會失敗,除非又有更多的可用空間。
<2>安裝另一個不同的new-handler函數。如果當前的new-handler函數不能產生更多的可用內存,可能它會知道另一個new-handler函數可以提供更多的資源。這樣的話,當前的new-handler可以安裝另一個new-handler來取代它(通過調用set_new_handler)。下一次operator new調用new-handler時,會使用最近安裝的那個。(這一策略的另一個變通辦法是讓new-handler可以改變它自己的運行行為,那么下次調用時,它將做不同的事。方法是使new-handler可以修改那些影響它自身行為的靜態或全局數據。)
<3>卸除new-handler,拋出標準異常。也就是傳遞空指針給set_new_handler。沒有安裝new-handler,operator new分配內存不成功時就會拋出一個標準的std::bad_alloc類型的異常。
<4>拋出std::bad_alloc或從std::bad_alloc繼承的其他類型的異常。這樣的異常不會被operator new捕捉,所以它們會被送到最初進行內存請求的地方。(拋出別的不同類型的異常會違反operator new異常規范。規范中的缺省行為是調用abort,所以new-handler要拋出一個異常時,一定要確信它是從std::bad_alloc繼承來的。想更多地了解異常規范,參見條款m14。)
<5>沒有返回,直接中斷退出。典型做法是調用abort或exit。abort/exit可以在標準c庫中找到(還有標準c++庫,參見條款49)。
二、寫類自己的new和set_new_handler
簡言之,分三步:1.class里面的"三樣"要齊全。
(1) set_new_handler
(2) operator new
(3) static new_handler currenthandler;
2.實現set_new_handler。
(1) 保存傳給它的任何指針,并返回在調用它之前所保存的任何指針。
(2) 保存傳給它的任何指針,并返回在調用它之前所保存的任何指針。
3.實現operator new。
(1) 調用標準set_new_handler函數,輸入參數為x的出錯處理函數。這使得x的new-handler函數成為全局new-handler函數。注意下面的代碼中,用了"::"符號顯式地引用std空間(標準set_new_handler函數就存在于std空間)。
(2) 調用全局operatornew分配內存。如果第一次分配失敗,全局operator new會調用x的new-handler,因為它剛剛(見1.)被安裝成為全局new-handler。如果全局operator new最終未能分配到內存,它拋出std::bad_alloc異常,x的operator new會捕捉到它。x的operator new然后恢復最初被取代的全局new-handler函數,最后以拋出異常返回。
(3) 假設全局operatornew為類型x的對象分配內存成功,, x的operator new會再次調用標準set_new_handler來恢復最初的全局出錯處理函數。最后返回分配成功的內存的指針。
c++是這么做的。
下面是一個"混合風格"(mixin-style)的基類,這種基類允許子類繼承特定的功能:建立一個類的new-handler的功能。
template<classt> // 提供類set_new_handler支持的
classnewhandlersupport {???? // 混合風格"的基類
public:
static new_handlerset_new_handler(new_handler p);
static void * operator new(size_tsize);
private:
static new_handler currenthandler;
};
template<classt>
new_handlernewhandlersupport<t>::set_new_handler(new_handler p)
{
new_handler oldhandler =currenthandler;
currenthandler = p;
return oldhandler;
}
template<classt>
void *newhandlersupport<t>::operator new(size_t size)
{
new_handler globalhandler =
std::set_new_handler(currenthandler);
void *memory;
try {
memory = ::operatornew(size);
}
catch (std::bad_alloc&) {
std::set_new_handler(globalhandler);
throw;
}
std::set_new_handler(globalhandler);
return memory;
}
// this sets eachcurrenthandler to 0
template<class t>
new_handlernewhandlersupport<t>::currenthandler;
=====================================
?
<條款8>寫operator new和operator delete時要遵循常規
一句話,系統缺省怎么做,你就怎么做。
一、new所要做的
1)要有正確的返回值;
2)可用內存不夠時要調用出錯處理函數(見條款7);
3)處理好0字節內存請求的情況;
4)避免不小心隱藏了標準形式的new(不過這是條款9的話題)。
這樣, operator new的偽代碼看起來會象下面這樣:
void * operator new(size_t size)??????????// operator new還可能有其它參數
{??????????????????????????????????????
if (size != sizeof(base))???????????????????? // 如果數量"錯誤",讓標準operator new
return ::operator new(size);????????????// 去處理這個請求????????????????????????????
???? while (1) {
分配size字節內存;
if (分配成功)
??????return (指向內存的指針);
// 分配不成功,找出當前出錯處理函數
????new_handler globalhandler = set_new_handler(0);
set_new_handler(globalhandler);
if (globalhandler) (*globalhandler)();
else throw std::bad_alloc();
??}
}
三、delete的任務
所要記住的只是,c++保證刪除空指針永遠是安全的,所以你要充分地應用這一保證。
偽代碼如下:
void base::operator delete(void *rawmemory, size_t size)
{
??if (rawmemory == 0) return;?????????????????? // 檢查空指針
??if (size != sizeof(base)) {???????????????? // 如果size"錯誤",
::operator delete(rawmemory);????????// 讓標準operator來處理請求
return;????????????????????????
??}
??釋放指向rawmemory的內存;
??return;
}
=====================================
<條款9>避免隱藏標準形式的new
在類里定義了一個稱為"operator new"的函數后,會不經意地阻止了對標準new的訪
問。條款50解釋了為什么會這樣,這里我們更關心的是如何想個辦法避免這個問題。
一個辦法是在類里寫一個支持標準new調用方式的operator new,它和標準new做同樣的事。這可以用一個高效的內聯函數來封裝實現。
另一種方法是為每一個增加到operator new的參數提供缺省值(見條款24)。
=====================================
<條款10>如果寫了operator new就要同時寫operator delete
問題1:為什么自己寫operator new 和 operator delete.(答:為了效率)
問題2:為什么要為自己的new寫自己的delete.(答:因為標準delete不知道自己new出了多大內存,見《深度探索c++對象模型》)
問題3:內存池.(詳情見eff??c++)
?
?
總結
以上是生活随笔為你收集整理的“伊凡 C普”第一式-内存管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大数据实践总结分享
- 下一篇: 大气湍流下的少模光纤耦合