フリー・ストア

C++フリー・ストア は、 プログラムの実行中に、オブジェクトのストレージを割り振り (および割り振り解除) できるメモリーのプールです。 new 演算子および delete 演算子は、それぞれ、フリー・ストアの割り振りと 割り振り解除に使用されます。

ユーザー専用のクラス用の newdelete を多重定義して、 独自バージョンを定義することができます。 new 演算子と delete 演算子に追加パラメーターを宣言することができます。 new 演算子と delete 演算子がクラス・オブジェクトに 使用されると、クラス・メンバー演算子関数の newdelete が、宣言してあれば、 呼び出されます。

クラス・オブジェクトを 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 関数を多重定義します。

#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 つしか持つことができません。

関連参照

IBM Copyright 2003