Item 51:newとdeleteを書くときは慣例に従ってください


Item 51: Adhere to convention when writing new and delete.
Item 50は、newdeleteをカスタマイズする方法を紹介していますが、あなたが従わなければならない慣例を説明していません.これらの慣例の中には直感的ではないものもあります.だから、覚えておく必要があります.
  • operator newリソースを無限ループで取得する必要があり、取得できなければ「new handler」を呼び出し、「new handler」が存在しない場合は異常を投げ出すべきである.
  • operator new処理すべきsize == 0の場合.
  • operator delete空のポインタと互換性があるべきである.
  • operator new/deleteメンバー関数としてsize > sizeof(Base)を扱うべき場合(継承が存在するため).
    外部operator new
    Item 49は、operator newをクラスのメンバー関数として再ロードする方法を示しています.ここではまず、外部(非メンバー関数)のoperator new:operator newを実装する方法を見てみましょう.メモリが不足している場合は「new handler」を呼び出し、申請サイズが0のメモリを要求する場合も正常に実行し、グローバルな(「normal form」)newを隠すことを避けることができます.
  • 戻り値を与えるのは簡単です.メモリが十分である場合、申請したメモリアドレスを返します.メモリが不足している場合、Item 49に記載されている規則に従って空または放出bad_alloc異常が返される.
  • 失敗するたびに「new handler」を呼び出し、申請メモリを繰り返すのは容易ではありません.「new handler」が空の場合にのみ例外を放出します.
  • 申請サイズがゼロの場合も合法的なポインタを返さなければならない.申請サイズがゼロのスペースを許可することは、確かにプログラミングに便利です.

  • 上記の目標を考慮すると、1つの非メンバー関数のoperator newは、概ね以下のように実現される.
    void * operator new(std::size_t size) throw(std::bad_alloc){
        if(size == 0) size = 1;
        while(true){
            //     
            void *p = malloc(size);
    
            //     
            if(p) return p;
    
            //     ,  new handler
            new_handler h = set_new_handler(0);
            set_new_handler(h);
    
            if(h) (*h)();
            else throw bad_alloc();
        }
    }
    
  • size == 0の場合、申請サイズは1とあまり適切ではありませんが、非常に簡単で正常に動作します.ましてや、大きさが0の空間をよく申請しないでしょう.
  • 2回set_new_handler呼び出しグローバル「new handler」を空に設定してから設定します.これは、「new handler」を直接取得できないため、マルチスレッド環境ではロックが必要です.
  • while(true)は、これがデッドサイクルである可能性があることを意味する.だからItem 49は、「new handler」がより多くのメモリを解放するか、新しい「new handler」をインストールするか、もしあなたが無駄な「new handler」を実現したら、ここはデッドサイクルです.

  • メンバーoperator new
    リロードoperator newは、通常、サブクラスに使用されるのではなく、特定のクラスに対して動的メモリ管理を最適化するためにメンバー関数として使用されます.Base::operator new()を実装する場合、オブジェクトサイズがsizeof(Base)であることに基づいてメモリ管理の最適化が行われるからである.
    もちろん、あなたが書いたBase::operator newはclassとそのサブクラス全体に通用する場合がありますが、このルールは適用されません.
    class Base{
    public:
        static void* operator new(std::size_t size) throw(std::bad_alloc);
    };
    class Derived: public Base{...};
    
    Derived *p = new Derived;       //     Base::operator new !
    

    サブクラス継承Base::operator new()の後、現在のオブジェクトは仮定のサイズではないため、この方法は現在のオブジェクトのメモリを管理するのに適していません.パラメータBase::operator newsizeで判断でき、サイズがsizeof(Base)でない場合、グローバルのnewを呼び出す.
    void *Base::operator new(std::size_t size) throw(std::bad_alloc){
        if(size != sizeof(Base)) return ::operator new(size);
        ...
    }
    

    上のコードはチェックしていませんsize == 0!これはC++の不思議な場所で、大きさが0の独立したオブジェクトがchar(Item 39参照)挿入されるので、sizeof(Base)は永遠に0ではないので、size == 0の場合は::operator new(size)に渡して処理しました.
    ここでは、operator new[]と同じパラメータと戻り値を持つoperator newについて説明します.これらのオブジェクトがいくつかあると仮定しないでください.また、各オブジェクトのサイズがいくらなのか、まだ存在しないオブジェクトを操作しないでください.理由:
  • 対象の大きさがわかりません.継承が発生した場合sizeが必ずしもsizeof(Base)に等しいとは限らないことも述べた.
  • size実パラメータの値は、これらのオブジェクトのサイズの和よりも大きい場合があります.Item 16に記載されているため、配列のサイズも格納する必要がある場合があります.

  • 外部operator deletenewよりもdeleteを実現するルールは簡単です.唯一注意しなければならないのはC++がdelete一つNULLが常に安全であることを保証しているので、この慣例を尊重すればいいです.
    同様に、外部(非メンバー)のdelete:
    void operator delete(void *rawMem) throw(){
        if(rawMem == 0) return; 
        //     
    }
    

    メンバーoperator delete
    メンバー関数のdeleteも簡単ですが、他のnewの申請を転送した場合は、sizeも他のdeleteの申請を転送する必要があります.
    class Base{
    public:
        static void * operator new(std::size_t size) throw(std::bad_alloc);
        static void operator delete(void *rawMem, std::size_t size) throw();
    };
    void Base::operator delete(void *rawMem, std::size_t size) throw(){
        if(rawMem == 0) return;     //      
        if(size != sizeof(Base)){
            ::operator delete(rawMem);
        }
        //     
    }
    

    上のチェックはsizeが空、rawMemが空ではありません.
    実際size実パラメータの値は、呼び出し元のタイプによって導出されます(虚析構造関数がなければ):
    Base *p = new Derived;  //   Base::~Base     
    delete p;               //   `delete(void *rawMem, std::size_t size)` `size == sizeof(Base)`。
    
    sizeBase::~Base()と宣言された場合、上記のvirtualは正しいsizeである.これも,Item 7が構造関数をsizeof(Derived)と宣言しなければならない理由である.
    本住所:http://harttle.com/2015/09/20/effective-cpp-51.html