フリー・ストア は、
プログラムの実行中に、オブジェクトのストレージを割り振り (および割り振り解除) できるメモリーのプールです。
new 演算子および delete 演算子は、それぞれ、フリー・ストアの割り振りと
割り振り解除に使用されます。
ユーザー専用のクラス用の new と delete を多重定義して、 独自バージョンを定義することができます。 new 演算子と delete 演算子に追加パラメーターを宣言することができます。 new 演算子と delete 演算子がクラス・オブジェクトに 使用されると、クラス・メンバー演算子関数の new と delete が、宣言してあれば、 呼び出されます。
クラス・オブジェクトを new 演算子で作成する場合、オブジェクト作成のために、 演算子関数の operator new() または operator new[]() (宣言済みの場合) のいずれかが呼び出されます。 クラスの operator new() や operator new[]() は、 キーワード static なしで宣言されている場合でも、 常に静的クラス・メンバーです。 この戻りの型は void* です。 その最初のパラメーターは、オブジェクト型のサイズで、std::size_t 型でなければなりません。 これを virtual にすることは、できません。
型 std::size_t は、インプリメンテーション依存の符号なし整数型で、標準ライブラリー・ヘッダー <cstddef> に定義されています。 new 演算子を多重定義するときは、戻り型が void* で、最初のパラメーターが std::size_t の クラス・メンバーとして宣言する必要があります。
operator new() または operator new[]() の宣言で、追加パラメーターを宣言できます。 割り振り式の中で、これらのパラメーターの値を指定する配置構文を使用します。
次の例は、2 つの operator new 関数を多重定義します。
静的配列 X::buffer は、3 つの Node オブジェクトを保持します。 各 Node オブジェクトは、data という名前の X オブジェクトを指すポインター、 および filled という名前のブール変数を含んでいます。 各 X オブジェクトは、number と呼ぶ整数を保管します。
この new 演算子を使用する場合、引き数 location を渡して、 新規の X オブジェクトを「作成」したい buffer の配列ロケーションを示します。 配列ロケーションが「filled」(filled のデータ・メンバーは、 配列ロケーションにおいては false と同じ) でなければ、new 演算子は、buffer[location] に配置された X オブジェクトを指すポインターを戻します。
#include <new> #include <iostream> using namespace std; class X; struct Node { X* data; bool filled; Node() : filled(false) { } }; class X { static Node buffer[]; public: int number; enum { size = 3}; void* operator new(size_t sz) throw (const char*) { void* p = malloc(sz); if (sz == 0) throw "Error: malloc() failed"; cout << "X::operator new(size_t)" << endl; return p; } void *operator new(size_t sz, int location) throw (const char*) { cout << "X::operator new(size_t, " << location << ")" << endl; void* p = 0; if (location < 0 || location >= size || buffer[location].filled == true) { throw "Error: buffer location occupied"; } else { p = malloc(sizeof(X)); if (p == 0) throw "Error: Creating X object failed"; buffer[location].filled = true; buffer[location].data = (X*) p; } return p; } static void printbuffer() { for (int i = 0; i < size; i++) { cout << buffer[i].data->number << endl; } } }; Node X::buffer[size]; int main() { try { X* ptr1 = new X; X* ptr2 = new(0) X; X* ptr3 = new(1) X; X* ptr4 = new(2) X; ptr2->number = 10000; ptr3->number = 10001; ptr4->number = 10002; X::printbuffer(); X* ptr5 = new(0) X; } catch (const char* message) { cout << message << endl; } }次に、上記の例の出力を示します。
X::operator new(size_t) X::operator new(size_t, 0) X::operator new(size_t, 1) X::operator new(size_t, 2) 10000 10001 10002 X::operator new(size_t, 0) Error: buffer location occupied
ステートメント X* ptr1 = new X は、X::operator new(sizeof(X)) を呼び出します。 ステートメント X* ptr2 = new(0) X は、X::operator new(sizeof(X),0) を呼び出します。
delete 演算子は、new 演算子 によって作成されたオブジェクトを破棄します。delete のオペランドは new によって 戻されたポインターでなければなりません。 デストラクターを持つオブジェクトに対して delete を呼び出すと、 オブジェクトの割り振り解除を行う前に、デストラクターが呼び出されます。
delete 演算子によってクラス・オブジェクトを破棄する場合、演算子関数の operator delete() または operator delete[]() (これが宣言されている場合) が呼び出され、これによってオブジェクトが破棄されます。 クラスの operator delete() や operator delete[]() は、 キーワード static なしで宣言されている場合でも、常に静的メンバーです。 この最初のパラメーターの型は void* でなければなりません。 operator delete() および operator delete[]() の戻りの型は void なので、これらは値を戻すことはできません。
次の例では、演算子関数 operator new() お よび operator delete() の宣言と使用方法について 説明します。
#include <cstdlib> #include <iostream> using namespace std; class X { public: void* operator new(size_t sz) throw (const char*) { void* p = malloc(sz); if (p == 0) throw "malloc() failed"; return p; } // single argument void operator delete(void* p) { cout << "X::operator delete(void*)" << endl; free(p); } }; class Y { int filler[100]; public: // two arguments void operator delete(void* p, size_t sz) throw (const char*) { cout << "Freeing " << sz << " byte(s)" << endl; free(p); }; }; int main() { X* ptr = new X; // call X::operator delete(void*) delete ptr; Y* yptr = new Y; // call Y::operator delete(void*, size_t) // with size of Y as second argument delete yptr; }
上記の例は、次の式と同じ出力を生成します。
X::operator delete(void*) Freeing 400 byte(s)
ステートメント delete ptr は、X::operator delete(void*) を呼び出します。 ステートメント delete yptr は、Y::operator delete(void*, size_t) を呼び出します。
削除されたオブジェクトにアクセスしようとした場合の結果は、削除後にオブジェクトの値が変化する 可能性があるので、未定義です。
演算子関数 new および delete を宣言していないクラス・オブジェクト、 または非クラス・オブジェクトに対して、new および delete を呼び出す場合、 グローバル演算子 new および delete が使用されます。 グローバル演算子 new および delete は、C++ ライブラリーに用意されています。
クラス・オブジェクトの配列の割り振りおよび割り振り解除用の C++ 演算子は、operator new[ ]() および operator delete[ ]() です。
delete 演算子を仮想として宣言できません。 ただし、基底クラスのデストラクターを仮想として宣言することで、delete 演算子に、 ポリモアフィックの振る舞いを追加できます。 次の例は、このことを示しています。
#include <iostream> using namespace std; struct A { virtual ~A() { cout << "~A()" << endl; }; void operator delete(void* p) { cout << "A::operator delete" << endl; free(p); } }; struct B : A { void operator delete(void* p) { cout << "B::operator delete" << endl; free(p); } }; int main() { A* ap = new B; delete ap; }
以下に、上の例の出力を示します。
~A() B::operator delete
ステートメント delete ap は、A のデストラクターが仮想として宣言されているので、 クラス A の代わりに、クラス B から delete 演算子を使用します。
delete 演算子からポリモアフィックの振る舞いを獲得できますが、 静的に可視である delete 演算子は、別の delete 演算子が呼び出されることがあっても、 アクセス可能にしておく必要があります。 例えば、上記の例で、代わりに B::operator delete(void*) が呼び出されても、 関数 A::operator delete(void*) をアクセス可能にしておく必要があります。
仮想デストラクターは、配列 (operator delete[]()) に対するどの割り振り解除演算子にも影響を与えません。 次の例は、このことを示しています。
#include <iostream> using namespace std; struct A { virtual ~A() { cout << "~A()" << endl; } void operator delete[](void* p, size_t) { cout << "A::operator delete[]" << endl; ::delete [] p; } }; struct B : A { void operator delete[](void* p, size_t) { cout << "B::operator delete[]" << endl; ::delete [] p; } }; int main() { A* bp = new B[3]; delete[] bp; };
ステートメント delete[] bp の振る舞いは、未定義です。
delete 演算子を多重定義するときは、前述のように、戻り型が void で、 最初のパラメーターの型が void* のクラス・メンバーとして宣言する必要があります。 宣言に型 size_t の 2 番目のパラメーターを追加することができます。 単一クラスには、operator delete() または operator delete[]() を 1 つしか持つことができません。
関連参照