Effective C++改訂2版のメモ(第2章)

第2章メモリ管理

- No.5 newとdeleteのペアは同じ形式に揃えよう

形式とは配列([])の有無のこと。

string *ary = new string[100];
delete []ary;

typedefを使うと、表面上配列指定の[]の有無が分からなくなるため、delete時に間違えやすいことが指摘されている。

typedef string addresslines[4];
string *ary = new addresslines;  // new string[4]と等しい
delete []ary;          // []が必要

vectorを使えば(vector)、このようなtypedefは不要

- No.6 デストラクタでポインタメンバにdeleteを使うのを忘れないようにしよう

クラスを長期間メンテしていて、ポインタのメンバを追加したとき、コンストラクタでそのメンバについてnewするのはまず忘れないが(動かなくなるので)、deleteを忘れやすい
なお、deleteにNULLポインタを渡すことは正しい。使われてない場合NULLを入れるようにしておけば、何も考えずdeleteを発行できる。

- No.7 メモリ不足に備えよう。

bad_alloc例外があがるが、この対処としてset_new_handlerを推奨している。これはシグナルハンドラのように、グローバルに設定されるもので、newでメモリ不足が起きた場合、new handler関数が呼ばれる。
この関数では、abortする、事前に取得していたメモリを開放する、等を行なう。
重要なのはメモリを開放しリトライすることが可能であり、この場合、無限ループとなる可能性がある。
後半で、あまり便利だとは思わないが、クラスごとのnew handlerを定義する方法を説明している。これはoperator newを再定義する。

void * X::operator new(size_t size)
{
   void *memory;
   new_handler prev = std::set_new_handler(my_handler); //登録
   try {
      memory = ::operator new(size);            //オリジナルのnewを呼ぶ
  } catch(std::bad_alloc){
     std::set_new_handler(prev);                //戻す
     throw;
   }
   std::set_new_handler(prev);                  //戻す
   return memory;
}

- No.8 operator newとoperator deleteを書くときは規約を守ろう

配列でnewした場合、継承したクラスでnewした場合、等にも対応しなければならないが、これらはoperator newの引数で指定されるサイズが(期待と)異なるので、ことなった場合は標準のnewを呼べばいい、ということが書かれている。
わざわざoperator newを定義してるのに、それでいいのかよくわからない。

- No.9 普通の形式のnewを隠蔽しないようにしよう

C++ではbaseクラスとderivedクラスで同じメソッドがある場合、baseクラスのメソッドを使えなくなるようにしているらしい。バグで誤ってbaseクラスの方が参照していても気づきにくいから、という理由らしい。この仕組みにより、operator newを定義すると、普通のnewが(特殊な指定無しでは)使えなくなる。
つまり、引数付きのnewを作ったとき、引数無しの(普通の)newがエラーとなる。

- No.10 operator newを書くならoperator deleteを書こう

operator newを書く理由として、メモリ管理の効率化がある。たとえば最初のnewのときに、後のnewで返す分の領域も確保して、フリーリストのような形で管理することで、メモリ取得・開放のコストを削除しようとする場合がある。この場合、deleteもフリーリストに戻すような処理に置き換える必要がある。