C++では、operator newとoperator deleteもオーバーロードすることが出来ます。しかし、幾分か厄介な注意点が存在します。
なお、以下の記述は非配列形式のnew/deleteを念頭に置いた記述ですが、配列形式であってもおそらく同じ注意点が当てはまるだろうと思います。
(1) あくまで、「operator new」「operator delete」のオーバーロードであるという点。
C++において、 というコードがあった場合、次のような挙動を示します。
operator new関数を呼び出す。→operator newの戻り値に対してオブジェクト構築(コンストラクタ呼び出し)。→オブジェクトのアドレスを返す。
つまり、new演算子というのは、「領域の確保」「コンストラクタ呼び出しによるオブジェクト構築」の二段階の手順を踏んでいるわけで、operator new関数の役割は、そのうちの第一段階、すなわち「領域の確保」にあります。operator new関数が第一引数にsize_t型の引数を取る理由は、それによって確保しなければならないバッファの大きさを知るためです。処理系によりますが、デフォルトのoperator newは第一引数でmallocを呼び出すという実装が真っ先に想起される実装方法ですね。
ちなみに、第二段階を書き換えることは基本的に出来ません。ということは、new演算子がオブジェクト構築以外に使われるということはありえないということになります。operator deleteも同じで、これも基本的には、"解体不要"な領域が引数として渡されることになります。つまり、渡された領域に対してデストラクタを呼び出すということはしてはいけません。
(2) delete演算子には引数を渡すことが出来ない。
operator new関数で2つ以上の引数を取るものを定義した場合、以下のように呼び出すことが出来ます。 ところが、deleteには同じような呼び出し法がありません。したがって、原則として、プラスアルファを引数に取るoperator newに対応するoperator deleteを、delete演算子を通して明示的に呼び出すことは出来ません。このような場合、次のような方法を取ることになります。
// SomeType* obj = new(X) SomeType;
obj->~SomeType() //明示的デストラクタ呼び出し
operator delete(obj,X); //明示的operator delete呼び出し
(3) operator newを定義した場合、対となるoperator deleteを必ず定義しなければならない。
デフォルトのoperator newをユーザー定義した場合、対応するoperator deleteもユーザーが適切に定義しなければ問題がおこるのは当然ですが、(2)で述べたとおり、原則としてdelete演算子では引数つきnewに対応するoperator deleteを呼び出すことは出来ませんから、operator deleteのオーバーロードではなく、例えばdestoryとかいう名前で別にオブジェクト解体と領域解放を行う関数を用意すればいいのではないか(一つの関数にまとめた方がミスも減りますし…)と思うかもしれません。
しかし、operator newを定義した場合、必ず対応するoperator deleteを定義しなければなりません。対応するoperator deleteとは、次のようなシグネチャを持つものをいいます。
void* operator new(std::size_t size,A a,B b,..,Z z);
// operator new↑ ↓operator delete
void operator delete(void* p,A a,B b,..,Z z);
or
void operator delete(void* p,std::size_t size,A a,B b,..,Z z);
この場合、対応するoperator deleteが定義されていれば、コンパイラはそれを呼び出すように実行コードを生成します。つまり、確保された領域はoperator deleteによって解放されるということになります。中には、placement newに対応するoperator deleteのように、何もしないということもありますが(placement new自体が受け取ったアドレスをそのまま返すだけなので、operator deleteで何もしようがない)。もちろん、先ほど述べたdestroy関数を定義することも問題はありません。重要なのは、対応するoperator deleteが適切に定義されていないと、new演算子で構築しようとするオブジェクトのコンストラクタが例外を投げることが出来なくなってしまう(投げるとメモリリークが発生してしまう可能性が高い)ということです。
[anchor=note1]*a[/anchor] operator new関数では、関数が領域確保に失敗した場合、関数にnothrow指定をしている場合はnullptrを返し、それ以外の場合はstd::bad_allocでcatchできる例外を投げなければならないとされています。