[セットトップ]Effective C++条項49:new-handlerの動作を理解する

8233 ワード

Openator newがメモリ割り当ての要件を満たすことができない場合、彼は例外を放出します(以前はnullを返します).operator newが例外を放出して満たされていないメモリ要件を反映する前に、顧客が指定したエラー処理関数、すなわちnew-handlerを呼び出します.この「メモリ不足を処理するために使用される」関数を指定するには、に宣言された標準ライブラリ関数であるset_new_handlerを呼び出す必要があります.
//set_new_handler  
namespace std {
    typedef void (*new_handler)();
	new_handler set_new_handler(new_handler p) throw();  //    ,            
}
        set_new_handlerのパラメータは、operator newが十分なメモリを割り当てられない場合に呼び出される関数を指すポインタです.戻り値もポインタでset_を指しますnew_handlerが呼び出される前に実行中の(すぐに置き換えられる)new-handler関数.set_new_handlerの使い方:
//set_new_handler  
//   operator new       ,       
void outOfMem()
{
	std::cerr << "Unable to satisfy request for memory
"; std::abort(); } int main() { std::set_new_handler(outOfMem); // int *pBigDataArray = new int [100000000L]; // , outOfMem; ... }

operator newがメモリ申請を満たすことができない場合、new-handler関数は、十分なメモリが見つかるまで呼び出されます.設計されたnew-handler関数は、次のことをしなければなりません.
1より多くのメモリを使用できるようにします.これによりoperator newの次のメモリ割り当て動作が成功する可能性があります.このポリシーを実装する方法の1つは、プログラムが実行されると大きなメモリが割り当てられ、new-handlerが初めて呼び出されたときにプログラムに使用されます.
2別のnew-handlerをインストールします.このnew-handlerが現在より多くの使用可能なメモリを取得できない場合、別のnew-handlerがこの能力を持っていることを知っています.このnew-handlerはこのnew-handlerをインストールして自分を置き換えることができます(set_new_handlerを呼び出す限り)、次にnew-handlerを呼び出すと、この新しくインストールされた関数が呼び出されます.これのもう一つの変奏は、new-handlerに自分の動作を修正させることであり、次に呼び出されると、いくつかの異なることをします.方法の1つは、new-handlerにnew-handlerの動作に影響を与えるstaticデータ、namespaceデータ、globalデータを修正させることです.
3 new-handlerをアンインストールします.つまりnullポインタをset_に渡します.new_handler、newがインストールされていないとhandler,operator newはメモリ割り当てが失敗したときに異常を放出します.
4スナップbad_alloc(またはbad_allocから派生した)の異常です.この異常はoperator newによって捕捉されないため、メモリの求められるところに伝播します.
5返さず、通常abortまたはexitを呼び出す
以上の選択は、new-handler関数を実装するときに大きな弾力性があります.
次のインプリメンテーションは、クラスごとにメモリ割り当てに失敗したときに呼び出される関数が異なるクラスに固有のメモリ割り当てに失敗した場合の処理関数です.
C++はクラス固有のnew-handlersをサポートしていない.しかし、私たちは自分でこのような行為を実現することができます.各クラスに独自のsetを提供するだけでnew_handlerとoperator newでいいです.set_new_handlerでは、クラス固有のnew-handlerを指定できます(標準set_new_handlerではglobal new-handlerを指定できます).operator newでは、クラスオブジェクトのメモリを割り当てる過程でglobal new-handlerをクラス固有のnew-handlerで置き換えることを保証します.以下のwidgetクラスでは、独自のnew-handlerを提供します.
//      new-handler,
class Widget
{
public:
     //         new-handler          ,
    static std::new_handler set_new_handler(std::new_handler p) throw();
    static void* operator new(std::size_t size) throw(std::bad_alloc); //   bad_alloc  
private:
    static std::new_handler currentHandler;  //        new-handler,        
};

set_の場合new_handlerのパラメータは、クライアントからしか伝わらない.静的関数はクラスのメンバー関数を呼び出すことができないため、new-handlerをstaticと定義すると、new-handler関数宣言に合致しないため、クライアント定義が必要である.この関数の役割は、この伝達された関数を保存することです.set_の場合new_handlerの作成は、まず取得したポインタを格納し、以前に格納したポインタを返します.これも標準版set_new_handler:
static std::new_handler Widget::set_new_handler(std::new_handler p) throw();
{
    std::new_handler oldHandler = currentHandler;
	currentHandler = p;
	return oldHandler;
}
Widgetのoperator newでは、次のことが必要です.
1標準のset_を呼び出すnew_handler(クラス内部を呼び出すべきではなく、役割が異なり、グローバルは保存されたcurrentHandlerをnewにインストールするためのグローバルhandlerであり、自分で定義したのはこれを保存するためであり、先にクライアントがクラスを呼び出すset_new_handlerだけがhandlerを保存し、operator newがこれをグローバルに設定する!soga!)Widgetのエラー処理関数を通知します.Wdigetのnew-handlerがglobal new-handlerにインストールされます.
2 global operator newを呼び出し、実際のメモリ割り当てを実行します.メモリの割り当てに失敗すると、global operator newはWidgetのnew-handlerを呼び出します.この関数はglobal new-handlerとしてインストールされたばかりなので.global operator newが最終的に十分なメモリを割り当てることができない場合、bad_が放出されます.alloc異常.この場合、Widgetのoperator newは元のglobal new-handlerを復元してから異常を伝播しなければならない.従来のnew-handlerが常に書き換えてインストールされることを確保するためにglobal new-handlerをリソースと見なし,リソース管理対象を用いてリソース漏洩を防止する.
3 global operator newが十分なWidgetオブジェクトに使用されるメモリを割り当てることができる場合、Widgetのoperator newはポインタを返し、割り当ての結果を指します.Widgetの構造関数はglobal new-handlerを管理し、Widget's operator newが呼び出される前のglobal new-handlerを自動的に復元します.
だから、注意しなければならないのは、まずグローバルを自分のnew-handlerに置き換えてクラスのhandlerとして、それからクラスが使い終わったら元のグローバルのhandlerを回復しなければなりません.変更したら、人に返さなければなりません.new-handlerはグローバルなので、1つのnewは同時にnew-handlerをインストールするしかありません.だから、回復を覚えておいてください.したがって、このnew-handlerリソースを管理するクラスを定義します.次のようになります.
  
//   Widget new-handler,           handler
class NewHandlerHolder
{
public:
	explicit NewHandlerHolder(std::new_handler nh) //     new-handler
		: handler(nh)
	{}

    ~NewHandlerHolder()
	{
		std::set_new_handler(handler);  //   ,       handler
	}
private:
	std::new_handler handler;  //    

	NewHandlerHolder(const NewHandlerHolder&);  //    
	NewHandlerHolder& operator=(const NewHandlerHolder&);
};

//      ,      Widget's operator new,     
void *Widget::operator new(std::size_t size) throw(std::bad_alloc)
{
   	//  Widget new-handler,       handler,       ,    null
	NewHandlerHolder h(std::set_new_handler(currentHandler));

	return ::operator new(size);  //         ,  global new-handler(  NewHandlerHolder       )       
}
は上記の手順を経て、Widgetの専属のhandlerの構造を完成させた.以下に、クライアント呼び出しの可能性を示します.
void outOfMem();  //     handler
Widget::set_new_handler(outOfMem); //  outOfMem Widget            

Widget *pw1 = new Widget;  //        ,  outOfMem

std::string* ps = new std::string;  //        ,  global new-handler,     

Widget::set_new_handler(0);  //  Widget    NULL
Wdiget* pw2 = new Widget;    //        ,      

上記のスキームはクラスによって異なるわけではないので、多重化することができます.多重化を実現する方法の1つは、クラスを継承して単一の特殊な能力を継承することを可能にするベースクラスを確立することである(この例では、クラス固有のnew-handlerを設定する能力であり、その後、このベースクラスをtemplateに変換し、derived classごとにエンティティが互いに異なるclass dataコンポーネントを取得するためである).
このベースクラスはderived classに必要なsetを継承させますnew_handler、operator new、templateセクションは、各derived classがエンティティが互いに異なるcurrentHandlerメンバー変数を取得することを保証します.コードは次のとおりです.
//         
template <typename T>
class NewHandlerSupport
{
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;
};

template <typename T>
std::new_handler
NewHandlerSuppport<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);
}

//      currentHandler    NULL
template <typename T>
std::new_handler NewHandlerSupport<T>::currentHandler = 0;

このテンプレートクラスがあれば、Widgetにset_を追加します.new_handlerのサポート機能は、NewHandlerSupportからクラスを継承すれば簡単です.次のようにします.
//  Widget   NewHandlerSupprt<Widget>               handler    
class Widget : public NewHandlerSupport<Widget>
{
	...
};
このテンプレートクラスでは、タイプパラメータTはクラスでは使用されず、NewHandlerSupportから継承されたクラスごとに、エンティティが互いに異なるNewHandlerSupportレプリカ(すなわち、独自のstaticメンバー変数currentHandlerを持つ)を持つ.タイプTは、異なる継承クラスを区別するためにのみ使用されます.テンプレートメカニズムは、各Tに対してcurrentHandlerを自動的に生成します.このクラスをテンプレート化されたベースクラスから継承し、自分でタイプパラメータを作成する有用なテクノロジーであり、名前もあります.「変なループテンプレートモード」(CRTP).
以前のoperator newは十分なメモリが割り当てられない場合nullを返すからです.したがって、互換性のために、従来の「割り当て失敗時にnullを返す」動作を担当するoperator newという別の形式が提供されています.nothrow形式になります.彼らはnewを使用するときにnothrowオブジェクト(newと定義)を使用しているからです.以下のようにします.
//nothrow new
class Widget {...};
Widget* pw1 = new Widget;  //   ,      bad_alloc
if (pw1 == 0) ... //    

Widget* pw2 = new (std::nothrow) Widget; //       ,  0
if (pw2 == 0) ... //      
Nothrow newは例外に対する強制保証性が高くない.式new(std::nothrow)Widgetで2つのことが起こり、1 nothrow版のoperator newが呼び出され、Widgetオブジェクトにメモリを割り当てるために使用され、割り当てに失敗したらnullに戻り、割り当てに成功するとWidgetコンストラクション関数が呼び出され、コンストラクション関数はnewのメモリを継続する可能性がある.さらにnothroew newを使用しない可能性があるため、この構造関数は異常を投げ、異常は依然として伝播する可能性があります.すなわちnothrow newを使用するとoperator newが異常を投げないこと、「new(std::nothrow)widget」が異常を起こさないことしか保証できないため、nothrowを運用する必要はないのが一般的である.
まとめ:
         set_new_handlerでは、メモリの割り当てに失敗したときに呼び出される関数を指定できます.
Nohrow newはメモリ割り当てにのみ適用されるため、非常に限られたツールです.後続のコンストラクション関数呼び出しは、例外を放出する可能性があります.