new 演算子と delete 演算子

C++ では、new 演算子と delete 演算子を使用して、オブジェクトの動的割り当てと割り当て解除をサポートしています。 これらの演算子は、フリー ストア (ヒープとも呼ばれます) と呼ばれるプールのオブジェクトにメモリを割り当てます。 new 演算子は特殊な関数 operator new を呼び出し、delete 演算子は特殊な関数 operator delete を呼び出します。

C ランタイム ライブラリと C++ 標準ライブラリのライブラリ ファイルの一覧については、「CRT ライブラリの機能」をご覧ください。

new 演算子

コンパイラは、次のようなステートメントを関数 operator new の呼び出しに変換します。

char *pch = new char[BUFFER_SIZE];

要求のサイズがゼロバイトのストレージである場合、operator new は別のオブジェクトへのポインターを返します。 つまり、operator new を繰り返し呼び出して、異なるポインターを返します。

割り当て要求のメモリが不足している場合、operator newstd::bad_alloc 例外をスローします。 または、配置フォームを使用した場合、またはスローoperator newしないサポートでリンクしている場合に返されますnullptrnew(std::nothrow)。 詳細については、「割り当てエラーの動作」を参照してください

operator new 関数の 2 つのスコープの説明を次の表に示します。

operator new 関数のスコープ

Operator Scope
::operator new グローバル
class-name::operator new クラス

最初の operator new 引数は型 size_tである必要があり、戻り値の型は常に void*です。

グローバルな operator new 関数は、new 演算子が、組み込み型のオブジェクト、ユーザー定義の operator new 関数を含まないクラス型のオブジェクト、および任意の型の配列の割り当てに使用されるときに呼び出されます。 operator new が定義されたクラス型のオブジェクトを割り当てるために new 演算子が使用されると、そのクラスの operator new が呼び出されます。

クラスに定義された operator new 関数は、そのクラス型のオブジェクトのグローバルな operator new 関数を隠す静的メンバー関数です (したがって仮想関数にはできません)。 new を使用してメモリを割り当て、指定した値に設定する場合を考えます。

#include <malloc.h>
#include <memory.h>

class Blanks
{
public:
    Blanks(){}
    void *operator new( size_t stAllocateBlock, char chInit );
};
void *Blanks::operator new( size_t stAllocateBlock, char chInit )
{
    void *pvTemp = malloc( stAllocateBlock );
    if( pvTemp != 0 )
        memset( pvTemp, chInit, stAllocateBlock );
    return pvTemp;
}
// For discrete objects of type Blanks, the global operator new function
// is hidden. Therefore, the following code allocates an object of type
// Blanks and initializes it to 0xa5
int main()
{
   Blanks *a5 = new(0xa5) Blanks;
   return a5 != 0;
}

new のかっこ内で指定された引数は、chInit 引数として Blanks::operator new に渡されます。 ただし、グローバルな operator new 関数は隠されているため、次のようなコードではエラーが生成されます。

Blanks *SomeBlanks = new Blanks;

コンパイラは、クラス宣言でメンバー配列 new 演算子と delete 演算子をサポートしています。 次に例を示します。

class MyClass
{
public:
   void * operator new[] (size_t)
   {
      return 0;
   }
   void   operator delete[] (void*)
   {
   }
};

int main()
{
   MyClass *pMyClass = new MyClass[5];
   delete [] pMyClass;
}

割り当てエラーの動作

C++ 標準ライブラリの関数は new 、C++ 98 以降の C++ 標準で指定された動作をサポートしています。 割り当て要求のメモリが不足している場合は、 operator new 例外を std::bad_alloc スローします。

以前の C++ コードでは、割り当てが失敗した場合に null ポインターが返されました。 スローしないバージョン newのコードがある場合は、プログラム nothrownew.objを . このファイルは nothrownew.obj 、グローバル operator new を、割り当てが失敗した場合に nullptr 返されるバージョンに置き換えます。 operator new がスローされ std::bad_allocなくなりました。 リンカー オプション ファイルとその他の詳細についてはnothrownew.obj、「リンク オプション」を参照してください

グローバルoperator newからの例外にチェックするコードと、同じアプリケーション内の null ポインターにチェックするコードを混在させることはありません。 ただし、動作が異なるクラスローカル operator new を作成することもできます。 この可能性は、コンパイラが既定で防御的に動作し、null ポインターの戻り値のチェックを呼び出しにnew含める必要があります。 これらのコンパイラのチェックを最適化する方法の詳細については、次を参照してください/Zc:throwingnew

メモリ不足の処理

式からの new 割り当ての失敗をテストする方法は、標準の例外メカニズムを使用するか、戻り値を nullptr 使用するかによって異なります。 標準 C++ では、アロケーターがスローされるか std::bad_alloc 、またはから std::bad_alloc派生したクラスをスローすることを想定しています。 このサンプルに示すように、このような例外を処理できます。

#include <iostream>
#include <new>
using namespace std;
#define BIG_NUMBER 10000000000LL
int main() {
   try {
      int *pI = new int[BIG_NUMBER];
   }
   catch (bad_alloc& ex) {
      cout << "Caught bad_alloc: " << ex.what() << endl;
      return -1;
   }
}

次の形式を nothrow 使用すると、次の newサンプルに示すように、割り当てエラーをテストできます。

#include <iostream>
#include <new>
using namespace std;
#define BIG_NUMBER 10000000000LL
int main() {
   int *pI = new(nothrow) int[BIG_NUMBER];
   if ( pI == nullptr ) {
      cout << "Insufficient memory" << endl;
      return -1;
   }
}

次に示すように、ファイルを使用 nothrownew.obj してグローバル operator new を置き換えたときに、メモリ割り当ての失敗をテストできます。

#include <iostream>
#include <new>
using namespace std;
#define BIG_NUMBER 10000000000LL
int main() {
   int *pI = new int[BIG_NUMBER];
   if ( !pI ) {
      cout << "Insufficient memory" << endl;
      return -1;
   }
}

失敗したメモリ割り当て要求のハンドラーを指定できます。 このようなエラーを処理するカスタム回復ルーチンを記述できます。 たとえば、予約済みメモリを解放してから、割り当てを再実行できます。 詳細については、_set_new_handlerを参照してください。

delete 演算子

new 演算子を使用して動的に割り当てられたメモリは、delete 演算子を使用して解放できます。 delete 演算子は operator delete 関数を呼び出し、この関数がメモリを解放して使用可能なプールに戻します。 delete 演算子を使用すると、クラス デストラクターも呼び出されます (存在する場合)。

グローバルとクラス スコープの operator delete 関数があります。 特定のクラスに対して定義できる operator delete 関数は、1 つだけです。定義されている場合、グローバルの operator delete 関数は隠されます。 グローバルの operator delete 関数は、常に任意の型の配列に対して呼び出されます。

グローバルの operator delete 関数。 グローバルの operator delete 関数とクラスメンバー operator delete 関数には、次の 2 つの形式があります。

void operator delete( void * );
void operator delete( void *, size_t );

特定のクラスに存在できるのは、上記の 2 つの形式のうち 1 つだけです。 最初の形式は、型 void * の 1 つの引数を受け取ります。この引数には、割り当てを解除するオブジェクトへのポインターが含まれています。 2 番目の形式であるサイズ割り当て解除は 2 つの引数を取ります。最初の引数は割り当て解除するメモリ ブロックへのポインターで、2 番目は割り当て解除するバイト数です。 両方の形式の戻り値の型は void です (operator delete は値を返すことができません)。

2 番目の形式の目的は、削除するオブジェクトの正しいサイズ カテゴリの検索を高速化することです。 多くの場合、この情報は割り当て自体の近くに格納されず、キャッシュされない可能性が高いです。 2 番目の形式は、基底クラスの operator delete 関数を派生クラスのオブジェクトの削除に使用するときに便利です。

operator delete 関数は静的であるため、仮想にすることはできません。 operator delete 関数は、「メンバー アクセス コントロール」で説明されているように、アクセス制御に従います。

次の例は、メモリの割り当てと解放を記録するように設計された、ユーザー定義の operator new 関数と operator delete 関数を示しています。

#include <iostream>
using namespace std;

int fLogMemory = 0;      // Perform logging (0=no; nonzero=yes)?
int cBlocksAllocated = 0;  // Count of blocks allocated.

// User-defined operator new.
void *operator new( size_t stAllocateBlock ) {
   static int fInOpNew = 0;   // Guard flag.

   if ( fLogMemory && !fInOpNew ) {
      fInOpNew = 1;
      clog << "Memory block " << ++cBlocksAllocated
          << " allocated for " << stAllocateBlock
          << " bytes\n";
      fInOpNew = 0;
   }
   return malloc( stAllocateBlock );
}

// User-defined operator delete.
void operator delete( void *pvMem ) {
   static int fInOpDelete = 0;   // Guard flag.
   if ( fLogMemory && !fInOpDelete ) {
      fInOpDelete = 1;
      clog << "Memory block " << cBlocksAllocated--
          << " deallocated\n";
      fInOpDelete = 0;
   }

   free( pvMem );
}

int main( int argc, char *argv[] ) {
   fLogMemory = 1;   // Turn logging on
   if( argc > 1 )
      for( int i = 0; i < atoi( argv[1] ); ++i ) {
         char *pMem = new char[10];
         delete[] pMem;
      }
   fLogMemory = 0;  // Turn logging off.
   return cBlocksAllocated;
}

上記のコードを "メモリ リーク" の検出に使うことができます。メモリ リークとは、フリー ストアに割り当てられ、解放されていないメモリを指します。 リークを検出するために、グローバルな new 演算子と delete 演算子がメモリの割り当てと解放をカウントするように再定義されています。

コンパイラは、クラス宣言でメンバー配列 new 演算子と delete 演算子をサポートしています。 次に例を示します。

// spec1_the_operator_delete_function2.cpp
// compile with: /c
class X  {
public:
   void * operator new[] (size_t) {
      return 0;
   }
   void operator delete[] (void*) {}
};

void f() {
   X *pX = new X[5];
   delete [] pX;
}