溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

深入淺析C++中的 new-handler機(jī)制

發(fā)布時(shí)間:2020-11-12 15:23:12 來(lái)源:億速云 閱讀:162 作者:Leah 欄目:開(kāi)發(fā)技術(shù)

深入淺析C++中的 new-handler機(jī)制?相信很多沒(méi)有經(jīng)驗(yàn)的人對(duì)此束手無(wú)策,為此本文總結(jié)了問(wèn)題出現(xiàn)的原因和解決方法,通過(guò)這篇文章希望你能解決這個(gè)問(wèn)題。

  當(dāng) operator new 不能滿足一個(gè)內(nèi)存分配請(qǐng)求時(shí),它拋出一個(gè) exception(異常)。很久以前,他返回一個(gè) null pointer(空指針),而一些比較老的編譯器還在這樣做。你依然能達(dá)到以前的目的(在一定程度上),但是我要到本文的最后再討論它。 

  在 operator new 因回應(yīng)一個(gè)無(wú)法滿足的內(nèi)存請(qǐng)求而拋出一個(gè) exception 之前,它先調(diào)用一個(gè)可以由客戶指定的被稱為 new-handler 的 error-handling function(錯(cuò)誤處理函數(shù))。(這并不完全確切,operator new 真正做的事情比這個(gè)稍微復(fù)雜一些,詳細(xì)細(xì)節(jié)將在下一篇文章中討論。)為了指定 out-of-memory-handling function,客戶調(diào)用 set_new_handler ——一個(gè)在 <new> 中聲明的標(biāo)準(zhǔn)庫(kù)函數(shù):

namespace std {
 typedef void (*new_handler)();
 new_handler set_new_handler(new_handler p) throw();
}

  就像你能夠看到的,new_handler 是一個(gè)指針的 typedef,這個(gè)指針指向不取得和返回任何東西的函數(shù),而 set_new_handler 是一個(gè)取得和返回一個(gè) new_handler 的函數(shù)。(set_new_handler 的聲明的結(jié)尾處的 "throw()" 是一個(gè) exception specification(異常規(guī)范)。它基本上是說(shuō)這個(gè)函數(shù)不會(huì)拋出任何異常,盡管真相更有趣一些。

  set_new_handler 的形參是一個(gè)指向函數(shù)的指針,這個(gè)函數(shù)是 operator new 無(wú)法分配被請(qǐng)求的內(nèi)存時(shí)應(yīng)該調(diào)用的。set_new_handler 的返回值是一個(gè)指向函數(shù)的指針,這個(gè)函數(shù)是 set_new_handler 被調(diào)用前有效的目標(biāo)。

  你可以像這樣使用 set_new_handler:

// function to call if operator new can't allocate enough memory
void outOfMem()
{
 std::cerr << "Unable to satisfy request for memory\n";
 std::abort();
}
int main()
{
 std::set_new_handler(outOfMem);
 int *pBigDataArray = new int[100000000L];
 ...
}

如果 operator new 不能為 100,000,000 個(gè)整數(shù)分配空間,outOfMem 將被調(diào)用,而程序?qū)⒃诎l(fā)出一個(gè)錯(cuò)誤信息后中止。(順便說(shuō)一句,考慮如果在寫這個(gè)錯(cuò)誤信息到 cerr... 的過(guò)程中內(nèi)存必須被動(dòng)態(tài)分配會(huì)發(fā)生什么。)

  當(dāng) operator new 不能滿足一個(gè)內(nèi)存請(qǐng)求時(shí),它反復(fù)調(diào)用 new-handler function 直到它能找到足夠的內(nèi)存。但是從這種高層次的描述已足夠推導(dǎo)出一個(gè)設(shè)計(jì)得好的 new-handler function 必須做到以下事情之一:

  ·Make more memory available(使得更多的內(nèi)存可用)。這可能使得 operator new 中下一次內(nèi)存分配的嘗試成功。實(shí)現(xiàn)這一策略的一個(gè)方法是在程序啟動(dòng)時(shí)分配一大塊內(nèi)存,然后在 new-handler 第一次被調(diào)用時(shí)釋放它供程序使用。 

  ·Install a different new-handler(安裝一個(gè)不同的 new-handler)。如果當(dāng)前的 new-handler 不能做到使更多的內(nèi)存可用,或許它知道有一個(gè)不同的 new-handler 可以做到。如果是這樣,當(dāng)前的 new-handler 能在它自己的位置上安裝另一個(gè) new-handler(通過(guò)調(diào)用 set_new_handler)。operator new 下一次調(diào)用 new-handler function 時(shí),它會(huì)得到最近安裝的那一個(gè)。(這個(gè)主線上的一個(gè)變化是讓一個(gè) new-handler 改變它自己的行為,這樣,下一次它被調(diào)用時(shí),可以做一些不同的事情。做到這一點(diǎn)的一個(gè)方法是讓 new-handler 改變能影響 new-handler 行為的 static(靜態(tài)),namespace-specific(名字空間專用)或 global(全局)的數(shù)據(jù)。) 

  ·Deinstall the new-handler(卸載 new-handler),也就是,將空指針傳給 set_new_handler。沒(méi)有 new-handler 被安裝,當(dāng)內(nèi)存分配沒(méi)有成功時(shí),operator new 拋出一個(gè)異常。 

  ·Throw an exception(拋出一個(gè)異常),類型為 bad_alloc 或繼承自 bad_alloc 的其它類型。這樣的異常不會(huì)被 operator new 捕獲,所以它們將被傳播到發(fā)出內(nèi)存請(qǐng)求的地方。 

  ·Not return(不再返回),典型情況下,調(diào)用 abort 或 exit。 

  這些選擇使你在實(shí)現(xiàn) new-handler functions 時(shí)擁有極大的彈性。

  有時(shí)你可能希望根據(jù)被分配 object 的不同,用不同的方法處理內(nèi)存分配的失?。?/p>

class X {
public:
 static void outOfMemory();
 ...
};
class Y {
public:
 static void outOfMemory();
 ...
};
X* p1 = new X; // if allocation is unsuccessful,
// call X::outOfMemory
 
Y* p2 = new Y; // if allocation is unsuccessful,
// call Y::outOfMemory

  C++ 沒(méi)有對(duì) class-specific new-handlers 的支持,但是它也不需要。你可以自己實(shí)現(xiàn)這一行為。你只要讓每一個(gè) class 提供 set_new_handler 和 operator new 的它自己的版本即可。class 的 set_new_handler 允許客戶為這個(gè) class 指定 new-handler(正像standard set_new_handler 允許客戶指定global new-handler)。class 的 operator new 確保當(dāng)為 class objects 分配內(nèi)存時(shí),class-specific new-handler 代替 global new-handler 被使用。

  假設(shè)你要為 Widget class 處理內(nèi)存分配失敗。你就必須清楚當(dāng) operator new 不能為一個(gè) Widget object 分配足夠的內(nèi)存時(shí)所調(diào)用的函數(shù),所以你需要聲明一個(gè) new_handler 類型的 static member(靜態(tài)成員)指向這個(gè) class 的 new-handler function。Widget 看起來(lái)就像這樣:

class Widget {
public:
 static std::new_handler set_new_handler(std::new_handler p) throw();
 static void * operator new(std::size_t size) throw(std::bad_alloc);
private:
 static std::new_handler currentHandler;
};

  static class members(靜態(tài)類成員)必須在 class 定義外被定義(除非它們是 const 而且是 integral),所以:

std::new_handler Widget::currentHandler = 0; // init to null in the class
// impl. file

  Widget 中的 set_new_handler 函數(shù)會(huì)保存?zhèn)鬟f給它的任何指針,而且會(huì)返回前次調(diào)用時(shí)被保存的任何指針,這也正是 set_new_handler 的標(biāo)準(zhǔn)版本所做的事情:

std::new_handler Widget::set_new_handler(std::new_handler p) throw()
{
 std::new_handler oldHandler = currentHandler;
 currentHandler = p;
 return oldHandler;
}

 最終,Widget 的 operator new 將做下面這些事情:

 以 Widget 的 error-handling function 為參數(shù)調(diào)用 standard set_new_handler。這樣將 Widget 的new-handler 安裝為 global new-handler。 

  調(diào)用 global operator new 進(jìn)行真正的內(nèi)存分配。如果分配失敗,global operator new 調(diào)用 Widget 的 new-handler,因?yàn)槟莻€(gè)函數(shù)剛才被安裝為 global new-handler。如果 global operator new 最后還是無(wú)法分配內(nèi)存,它會(huì)拋出一個(gè) bad_alloc exception。在此情況下,Widget 的 operator new 必須恢復(fù)原來(lái)的 global new-handler,然后傳播那個(gè) exception。為了確保原來(lái)的 new-handler 總能被恢復(fù),Widget 將 global new-handler 作為一種資源對(duì)待,并遵循《C++箴言:使用對(duì)象管理資源》中的建議,使用 resource-managing objects(資源管理對(duì)象)來(lái)預(yù)防 resource leaks(資源泄漏)。 

  如果 global operator new 能夠?yàn)橐粋€(gè) Widget object 分配足夠的內(nèi)存,Widget 的 operator new 返回一個(gè)指向被分配內(nèi)存的指針。object 的用于管理 global new-handler 的 destructor(析構(gòu)函數(shù))自動(dòng)將 global new-handler 恢復(fù)到調(diào)用 Widget 的 operator new 之前的狀態(tài)。 

  以下就是你如何在 C++ 中表達(dá)這所有的事情。我們以 resource-handling class 開(kāi)始,組成部分中除了基本的 RAII 操作(在構(gòu)造過(guò)程中獲得資源并在析構(gòu)過(guò)程中釋放)(《C++箴言:使用對(duì)象管理資源》),沒(méi)有更多的東西:

class NewHandlerHolder {
public:
 explicit NewHandlerHolder(std::new_handler nh) // acquire current
 :handler(nh) {} // new-handler
 
 ~NewHandlerHolder() // release it
 { std::set_new_handler(handler); }
private:
 std::new_handler handler; // remember it
 
 NewHandlerHolder(const NewHandlerHolder&); // prevent copying
 NewHandlerHolder& // (see 《C++箴言:謹(jǐn)慎考慮資源管理類的拷貝行為》)
 operator=(const NewHandlerHolder&);
};

  這使得 Widget 的 operator new 的實(shí)現(xiàn)非常簡(jiǎn)單:

void * Widget::operator new(std::size_t size) throw(std::bad_alloc)
{
 NewHandlerHolder // install Widget's
 h(std::set_new_handler(currentHandler)); // new-handler
 
 return ::operator new(size); // allocate memory
 // or throw
 
} // restore global
// new-handler

  Widget 的客戶像這樣使用它的 new-handling capabilities(處理 new 的能力):

void outOfMem(); // decl. of func. to call if mem. alloc.
// for Widget objects fails
 
Widget::set_new_handler(outOfMem); // set outOfMem as Widget's
// new-handling function
 
Widget *pw1 = new Widget; // if memory allocation
// fails, call outOfMem
 
std::string *ps = new std::string; // if memory allocation fails,
// call the global new-handling
// function (if there is one)
 
Widget::set_new_handler(0); // set the Widget-specific
// new-handling function to
// nothing (i.e., null)
 
Widget *pw2 = new Widget; // if mem. alloc. fails, throw an
// exception immediately. (There is
// no new- handling function for
// class Widget.)

  無(wú)論 class 是什么,實(shí)現(xiàn)這個(gè)方案的代碼都是一樣的,所以在其它地方重用它就是一個(gè)合理的目標(biāo)。使它成為可能的一個(gè)簡(jiǎn)單方法是創(chuàng)建一個(gè) "mixin-style" base class(“混合風(fēng)格”基類),也就是說(shuō),一個(gè)設(shè)計(jì)為允許 derived classes(派生類)繼承一個(gè)單一特定能力(在當(dāng)前情況下,就是設(shè)定一個(gè) class-specific new-handler 的能力)的 base class(基類)。然后把這個(gè) base class(基類)轉(zhuǎn)化為一個(gè) template(模板),以便于你得到針對(duì)每一個(gè) inheriting class(繼承來(lái)的類)的 class data 的不同拷貝。

  這個(gè)設(shè)計(jì)的 base class(基類)部分讓 derived classes(派生類)繼承它們?nèi)夹枰?set_new_handler 和 operator new functions,而這個(gè)設(shè)計(jì) template(模板)部分確保每一個(gè) inheriting class(繼承來(lái)的類)得到一個(gè)不同的 currentHandler data member(數(shù)據(jù)成員)。這聽(tīng)起來(lái)可能有點(diǎn)復(fù)雜,但是代碼看上去可靠而且熟悉。實(shí)際上,僅有的真正不同是它現(xiàn)在可以用在任何需要它的 class 之上:

template<typename T> // "mixin-style" base class for
class NewHandlerSupport{
 // class-specific set_new_handler
public: // support
 
 static std::new_handler set_new_handler(std::new_handler p) throw();
 static void * operator new(std::size_t size) throw(std::bad_alloc);
 
 ... // other versions of op. new
private:
 static std::new_handler currentHandler;
};
 
template<typename T>
std::new_handler
NewHandlerSupport<T>::set_new_handler(std::new_handler p) throw()
{
 std::new_handler oldHandler = currentHandler;
 currentHandler = p;
 return oldHandler;
}
 
template<typename T>
void* NewHandlerSupport<T>::operator new(std::size_t size)
throw(std::bad_alloc)
{
 NewHandlerHolder h(std::set_new_handler(currentHandler));
 return ::operator new(size);
}
// this initializes each currentHandler to null
template<typename T>
std::new_handler NewHandlerSupport<T>::currentHandler = 0;

  有了這個(gè) class template(類模板),為 Widget 增加 set_new_handler 支持就很容易了:Widget 只需要從 NewHandlerSupport<Widget> 繼承即可。(可能看起來(lái)很奇特,但是下面我將解釋更多的細(xì)節(jié)。)

class Widget: public NewHandlerSupport<Widget> {
 ... // as before, but without declarations for
}; // set_new_handler or operator new

  這些就是 Widget 為了提供一個(gè) class-specific set_new_handler 所需要做的全部。

  但是也許你依然在為 Widget 從 NewHandlerSupport<Widget> 繼承而煩惱。如果是這樣,當(dāng)你注意到 NewHandlerSupport template 從來(lái)沒(méi)有用到它的 type parameter T 時(shí),你可能會(huì)更加煩惱。它不需要那樣做。我們需要的全部就是為每一個(gè)從 NewHandlerSupport 繼承的 class 提供一份不同的 NewHandlerSupport ——特別是它的 static data member(靜態(tài)數(shù)據(jù)成員)currentHandler ——的拷貝。template parameter T 只是為了將一個(gè) inheriting class 同另一個(gè)區(qū)分開(kāi)來(lái)。template 機(jī)制自己自動(dòng)地為每一個(gè)被實(shí)例化的 NewHandlerSupport 中的 T 生成一個(gè) currentHandler 的拷貝。 

  對(duì)于 Widget 從一個(gè)把 Widget 當(dāng)作一個(gè) type parameter(類型參數(shù))的 templatized base class(模板化基類)繼承,如果這個(gè)概念把你弄得有點(diǎn)糊涂,不必難受。它最開(kāi)始對(duì)每一個(gè)人都有這種影響。然而,它發(fā)展成如此有用的一項(xiàng)技術(shù),它有一個(gè)名字,雖然它正常看上去所反映的事實(shí)并不是他們第一次看到它的樣子。它被稱作 curiously recurring template pattern(奇特的遞歸模板模式) (CRTP)。真的。

  在這一點(diǎn)上,我發(fā)表了一篇文章建議一個(gè)更好的名字叫做 "Do It For Me",因?yàn)楫?dāng) Widget 從 NewHandlerSupport<Widget> 繼承時(shí),它其實(shí)是在說(shuō):“我是 Widget,而我要從針對(duì) Widget 的 NewHandlerSupport class 繼承?!睕](méi)有人使用我提議的名字(甚至是我自己),但是把 CRTP 考慮成說(shuō) "do it for me" 的一種方式也許會(huì)幫助你理解 templatized inheritance(模板化繼承)在做些什么。

  像 NewHandlerSupport 這樣的 templates 使得為任何有需要的 class 添加一個(gè) class-specific new-handler 變得易如反掌。然而,mixin-style inheritance(混合風(fēng)格繼承)總是會(huì)導(dǎo)致 multiple inheritance(多繼承)的話題,而在我們沿著這條路走下去之前,你需要閱讀《C++箴言:謹(jǐn)慎使用多繼承》。

  直到 1993 年,C++ 還要求 operator new 不能分配被請(qǐng)求的內(nèi)存時(shí)要返回 null。operator new 現(xiàn)在則被指定拋出一個(gè) bad_alloc exception,但是很多 C++ 程序是在編譯器開(kāi)始支持這個(gè)修訂標(biāo)準(zhǔn)之前寫成的。C++ 標(biāo)準(zhǔn)化委員會(huì)不想遺棄這些 test-for-null(檢驗(yàn)是否為 null)的代碼基礎(chǔ),所以他們提供了 operator new 的另一種可選形式,用以提供傳統(tǒng)的 failure-yields-null(失敗導(dǎo)致 null)的行為。這些形式被稱為 "nothrow" 形式,這在一定程度上是因?yàn)樗鼈冊(cè)谑褂?new 的地方使用了 nothrow objects(定義在頭文件 <new> 中):

class Widget { ... };
Widget *pw1 = new Widget; // throws bad_alloc if
// allocation fails
 
if (pw1 == 0) ... // this test must fail
 
Widget *pw2 =new (std::nothrow) Widget; // returns 0 if allocation for
// the Widget fails
 
if (pw2 == 0) ... // this test may succeed

  對(duì)于異常,nothrow new 提供了比最初看上去更少的強(qiáng)制保證。在表達(dá)式 "new (std::nothrow) Widget" 中,發(fā)生了兩件事。首先,operator new 的 nothrow 版本被調(diào)用來(lái)為一個(gè) Widget object 分配足夠的內(nèi)存。如果這個(gè)分配失敗,眾所周知,operator new 返回 null pointer。然而,如果它成功了,Widget constructor 被調(diào)用,而在此刻,所有打的賭都失效了。Widget constructor 能做任何它想做的事。它可能自己 new 出來(lái)一些內(nèi)存,而如果它這樣做了,它并沒(méi)有被強(qiáng)迫使用 nothrow new。那么,雖然在 "new (std::nothrow) Widget" 中調(diào)用的 operator new 不會(huì)拋出,Widget constructor 卻可以。如果它這樣做了,exception 像往常一樣被傳播。結(jié)論?使用 nothrow new 只能保證 operator new 不會(huì)拋出,不能保證一個(gè)像 "new (std::nothrow) Widget" 這樣的表達(dá)式絕不會(huì)導(dǎo)致一個(gè) exception。在所有的可能性中,你最好絕不需要 nothrow new。

  無(wú)論你是使用 "normal"(也就是說(shuō),exception-throwing)new,還是它的稍微有些矮小的堂兄弟,理解 new-handler 的行為是很重要的,因?yàn)樗梢杂糜趦煞N形式。

  Things to Remember

  ·set_new_handler 允許你指定一個(gè)當(dāng)內(nèi)存分配請(qǐng)求不能被滿足時(shí)可以被調(diào)用的函數(shù)。 

  ·nothrow new 作用有限,因?yàn)樗鼉H適用于內(nèi)存分配,隨后的 constructor 調(diào)用可能依然會(huì)拋出 exceptions。

看完上述內(nèi)容,你們掌握深入淺析C++中的 new-handler機(jī)制的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI