new と delete (3) -- 配列の new
placement new に関して、もう一つ大事なことがあった。
それは、配列の new、つまり、operator new[] である。
operator new と operator new[] は別物だ。
operator new を定義したからといって、new Foo[10];
などとやったときに、そのオーバーロードされた operator new が呼ばれるわけではない。
class X { };
class Foo {
public:
static void *operator new( size_t size, X& x );
};
X x;
new(x) Foo[10]; // コンパイルエラー! (適切な operator new[] が定義されていない)
というわけで、次のように operator new[] も定義してやる必要がある。
class Foo {
public:
static void *operator new( size_t size, X& x );
static void *operator new[]( size_t size, X& x );
};
で、問題は、この operator new[] の第1引数に渡ってくる size
の値が不定だということなのである。まあ、不定というのは言いすぎかもしれないが、
C++ の標準規格でも次のような言い方がなされている [5.3.4.12]。
ここで、x と y は非負の unspecified な値であり、 配列の割り当てにおけるオーバーヘッドを表している。
new T[5]はoperator new[](sizeof(T)*5+x)を呼び出すnew(2,f) T[5]はoperator new[](sizeof(T)*5+y,2,f)を呼び出す
この x なり y なりの値は、処理系ごとに異なるし、さらには同一の処理系であっても、 状況によって異なりうる。 たとえば、g++ では、オブジェクトに対してデストラクタを呼ぶ必要がない場合は x が 0 になるが、そうでなければ x は 4 になる (試してみた限り)。 おそらくは、この余分な 4バイトの領域には配列内のオブジェクトの個数、 すなわちデストラクタを呼び出すべき回数が格納されるのであろう。
したがって、配列 new の placement バージョンを定義しようと思った場合、
次のような形であらかじめバッファを確保しておくようなやり方は使えないし、
バッファの先頭が配列の先頭になるとも限らない、ということになる。
class Foo {
public:
static void *operator new[]( size_t size, void *p ) {
return p;
}
~Foo() { }
};
char buf[ sizeof(Foo) * 5 ];
Foo *p = new( buf ) Foo[5]; // メモリ領域が 4バイト足りない ⇒ スタックを壊す!
// また、(void *)p != (void *)buf だ!
つまりは、ちゃんと、operator new[] に渡された size
を使って動的にメモリ領域を確保してやらなければならないということだ。
初出: 2002年6月13日