Effective C++ノート第8部カスタムnewとdelete

10543 ワード

49.new-handlerの挙動を理解する(Understand the behavior of the new-handler)
set_new_handlerでは、メモリ割り当てが満たされない場合に呼び出される関数を指定できます.Nothrow newはメモリ割り当てにのみ適用されるため、限られたツールです.後続のコンストラクション関数呼び出しは、例外を放出する可能性があります.Openator newがメモリ割り当ての要件を満たすことができない場合、彼はまず顧客が指定したエラー処理関数、いわゆるnew-handlerを呼び出します.この関数はパラメータがなくvoidを返します.
void outOfMem(){
    std::cerr<<"Unable to satisfy request for memory
"
; std::abort();// , outOfMme() } int main(int argc, const char * argv[]) { std::set_new_handler(outOfMem); int* p = new int[9999999999999999999L]; return 0; } // Unable to satisfy request for memory

良いnew-handlerは以下のことをしなければならない:1.より多くのメモリを使用できるようにします.2.別のnew-handlerをインストールします.new-handlerがより多くのメモリを取得できない場合、どのnew-handlerがより多くのメモリを取得できるか知っている場合は、new-handlerに設定します.3.new-handlerを取り外します.nullをset_に渡すnew_handlerを取り外します.これによりnewはメモリ割り当てに失敗したときに異常を放出します.4.badを投げ出す_allocの異常.このような異常はoperator newに捕捉されないため、メモリリクエストに伝播します.5.返さずにabortまたはexitを通常呼び出します.
時々new_handlerはclassにバインドされ、異なるclassのoperator newがメモリ割り当ての要件を満たすことができない場合、異なるnew_を呼び出します.handler.この機能をNewHandlerSupportにカプセル化します.

//  RAII  
//NewHandlerSupport     typename T
//     template               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);
    NewHandlerSupport() = default;

    explicit NewHandlerSupport(std::new_handler nh)
    { handler = nh; }//    new_handler;

    ~NewHandlerSupport(){
        //       new_handler    
        std::set_new_handler(handler);
    }
private:
    static std::new_handler currentHandler;//         new_handler
    static std::new_handler handler;//      new_handler

    NewHandlerSupport(const NewHandlerSupport&);//  copying
    NewHandlerSupport& operator=(const NewHandlerSupport*);
};

template<typename T>
std::new_handler NewHandlerSupport<T>::set_new_handler(std::new_handler p){
    std::new_handler oldHandler = currentHandler;//   new_handler
    currentHandler = p;//   new_handler
    return oldHandler;//   new_handler
}

template<typename T>
void* NewHandlerSupport<T>::operator new(std::size_t size)
throw(std::bad_alloc)
{
    //  currentHandler new_handler
    //     ——  new_handler         handler
    NewHandlerSupport h(std::set_new_handler(currentHandler));
    return ::operator new(size);
    //   h     ,   new_handler    
}

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

class Widget: public NewHandlerSupport<Widget>{
public:
    Widget()
    {
    }
    ~Widget();
    int a[99999999999999999L];//      
};

void outOfMem(){
    std::cerr<<"Unable to satisfy request for memory
"
; std::abort(); } int main(int argc, const char * argv[]) { Widget::set_new_handler(outOfMem); Widget* pw = new Widget();// outOfMem // new_handler return 0; }

50.newとdeleteの合理的な置換タイミングを理解する(Understand when it makes sense to replace new and delete e.)
パフォーマンスの改善、heap運用エラーのデバッグ、heap使用情報の収集など、独自のnewとdeleteを書く理由はたくさんあります.newとdeleteをリロードする理由:1.運用上のエラーを検出するために使用します.たとえば、newをカスタマイズして、メモリを過剰に割り当てたり、お客様が得たメモリブロックの前後に特定のbyte署名を配置したりすることができます.プログラミングエラーによってoverruns(ディスペンサブロックの末端に書き込みポイントがある)やunderruns(始点より前)が発生した場合、カスタムdelete種で検出して記録することができます.2.動的割当てメモリの使用統計を収集する.3.分配と返還の速度を上げるため.4.デフォルトのメモリマネージャによる余分なコストを削減します.5.デフォルトのディスペンサの最適でない位置を補うため.6.関連オブジェクトをギャザーセットにするために.欠ページ率を下げる.7.非伝統的な行為を得るため.
51.newとdeleteの作成には、通常(Adhere to convention when writing new and delete e.)を守らなければならない.
Operator newには無限のループが含まれているはずで、メモリの要件を満たすことができない場合はnew-handlerを呼び出すべきです.0 bytes申請を処理する能力もあるはずです.class専属バージョンでは、「正しいサイズより大きい(エラー)申請」も処理する必要があります.Operator deleteはnullポインタを受け取ったときに何もしないはずです.class専属バージョンでは、「正しいサイズより大きい(エラー)申請」も処理する必要があります.Operator newで0 byte申請を処理する方法は1 byte申請と見なす.c++は顧客に0 byteを要求し、operator newも合法的なポインタを返さなければならない.
if(size == 0){
    size = 1;
}

operator newは、正確なサイズよりも大きな申請を正しく処理する必要があります.この場合、derived classはoperator new付きbase classを継承しますが、derived classはoperator newを宣言していないため、new derived();はbase classのoperator newを呼び出します.解決策は、このような場合に標準のoperator newを呼び出すことである.
if(size != sizeof(Base)){
    return ::operator new(size);
}

このアプローチには、sizeof(Base)が0を返さないため、0 byte申請の処理が含まれる.なぜならnewを実現する必要があるからですhandlerはoperator newで無限ループを提供し、割り当てが成功しない限りreturnを呼び出すか、new_を呼び出す.handler;
while(true){
        size bytes;
    if(    )
    returnif(new_handler != NULL)
      new_handler;
    else
    throw std::bad_alloc();

}

operator new[]を実装するには、array内にこれまで存在しなかったオブジェクトに何もできないため、未加工のメモリを割り当てる必要があります.継承が含まれているため、各オブジェクトのサイズさえ分かりません.
Operator deleteはnullポインタの削除を保証する必要があります.
if(rawMemory == 0) 
return;

Operator deleteは、正しいサイズよりも大きな(エラー)申請を正しく処理する必要があります.原理はnewと同じです.
if(size != sizeof(Base)){
    ::operator delete(rawMemory);
    return;
}

52.placement newもplacement delete(Write placement delete if you write placement new.)
placement operator newを書くと、対応するplacement operator deleteも書かれていることを確認してください.そうしないと、プログラムに暗黙的で断続的なメモリ漏洩が発生する可能性があります.placement newとplacement deleteを宣言すると、無意識に(わざと)正常なバージョンを隠さないでください.式を次のように書きます.
Widget* pw = new Widget();

メモリを割り当てるoperator newとwidgetのdefaultコンストラクション関数の2つの関数が呼び出されます.最初の関数が成功すると、2番目の関数は異常を放出します.C++はnewに対応するdeleteを探してメモリを解放します.しかし、class専用のoperator newと書いたら、それ以外はきっとsize_tパラメータには他のパラメータがあり,このようなoperator newはplacement newとなる.placementの場合、newが成功したが構築に失敗した場合、コンパイラはplacement newと同じパラメータのplacement deleteを探します.対応するplacement deleteが宣言されていないと、何もせず、メモリが漏洩します.