New演算子

出典: フリー百科事典『ウィキペディア(Wikipedia)』
移動: 案内検索

newまたはNewは、C++を始めとしたオブジェクト指向プログラミング言語において、インスタンスを作成する演算子である。多くの場合、ヒープ領域からの動的メモリ確保(動的記憶域確保)を伴う。

new演算子によるインスタンスの作成は、大きく分けて、記憶域を確保することと初期化を行うことに分けられる。記憶域を確保する処理は、多くの場合言語の処理系が用意するが、後述するC++のようにプログラム内で独自に定義できるものもある。初期化は、コンストラクタを呼ぶことで行われ、プログラム内で自由に定義できることが一般的である。

概要[編集]

C++やC++の影響を受けた言語では、概ね次のような構文となっている。

var = new T

varは、作成されたインスタンスへの参照を保持するポインタ型もしくは参照型の変数である。Tは、作成されるインスタンスのデータ型を指定する。Tがクラスの場合、Tのインスタンスを生成するためにデフォルトコンストラクタが呼ばれる。クラス以外の型を指定可能かどうかは言語による。

次のように、newでインスタンスを生成する際には、初期化子を指定できる。

var = new T(init);

initは初期化に用いる値を表す。Tがクラス型の場合、これを引数にしてコンストラクタが呼ばれる。

また、配列を作成することも可能である。なお、C++では、これをnew[]演算子として、new演算子と区別している。一方、JavaやC#では、new演算子の構文の一種として扱っている。

var = new T[size];

sizeは、作成する要素数を指定する。なお、C++では配列をnew[]で作成する場合、初期化子が指定不可能なため、常にデフォルトコンストラクタが呼ばれる。一方、JavaC#では、次のようにnewした配列に初期化子を与えられる。

var = new T[size] {init1, init2, init3};

初期化子を指定した場合、sizeは省略可能である。また、JavaやC#の配列は、常にnew演算子でヒープに作られる存在であり、下のようなの配列変数の初期化は、上に対する糖衣構文となっている。

T[] var1 = new T[size] {init1, init2, init3}; //new演算子を使用
T[] var2 = {init1, init2, init3}; //配列初期化の構文を使用、上と同じ意味

エラー処理[編集]

newで記憶域確保に失敗すると、多くの場合、例外が投げられる。そのため、mallocなどと違い、newの結果は常に正当なオブジェクトを指し示しているものとして扱うことが可能である。ただし、古いコンパイラではNULLを返すものがあるので注意が必要である。

言語ごとの詳細[編集]

C++[編集]

C++のnew演算子とnew[]演算子は、まず、同名の演算子関数(後述)で記憶域を確保し、次にコンストラクタを呼んでインスタンスの初期化を行う。C++のnew演算子という名称は、Simulaの同名の演算子に由来する。

deleteとdelete[]演算子[編集]

newやnew[]で記憶域を確保して生成したインスタンスは、delete及びdelete[]演算子で破棄されなければならない。さもなくば、メモリリークを引き起こしてしまう。多くの場合、deleteをデストラクタに任せるRAIIパターンが利用される。

#include <boost/scoped_ptr.hpp>
 
class Bar { /* ... */ };
 
void f()
{
    boost::scoped_ptr<Bar> b(new Bar);
 
    //...
 
} //有効範囲から外れるここでbのデストラクタが実行される。scoped_ptr<>のデストラクタがdeleteを行う。

new演算子関数[編集]

new演算子とnew[]演算子での記憶域の確保を制御するために、これらの演算子は多重定義が可能である。そうして定義されたnew、new[]演算子関数は、new及びnew[]演算子での記憶域確保に使用される。そして、delete及びdelete[]演算子の記憶域の解放には、delete、delete[]演算子関数が使用される。

クラス内に設置した場合、そのクラスと派生クラスをnew演算子で作成する際の記憶域の確保に使用される。なおクラス内に置いた場合、staticを指定しなくても、自動的に静的メンバ関数として扱われる (X3014 12.5)。

class hoge
{
public:
    static void* operator new(std::size_t);
    static void* operator new[](std::size_t);
    static void operator delete(void*);
    static void operator delete[](void*);
};

new hogeという式は次のように実行される。

  1. まず、new演算子関数の名前探索を行う(この例では、hoge::operator newが見付かる)。
  2. sizeof (hoge)の値を引数にしてnew演算子関数を呼び、記憶域確保を行う。
  3. new演算子関数が返したポインタの指す位置をthisポインタとして、コンストラクタを呼び、インスタンスを生成する。

new[]演算子関数がnew演算子関数と分かれている理由は、『C++の設計と進化』によれば、型Tの配列がTのオブジェクトではないという方針により、Tの配列を確保するためにTのnew演算子関数を使うわけには行かないと考えられたためである。そこで別途new[]演算子関数を設けることにしたのである (§10.3)。

なお、new T[n]としたとき、new[]演算子関数にはsizeof (T) * nよりも大きい値が引数に渡される可能性がある。これは、主にdelete[]で解放するときにデストラクタを呼ぶ回数(配列の要素数)を記録するためなどといった理由によるものである。

また、newとnew[]演算子関数は、クラスの外、名前空間内にも定義でき、new及びnew[]演算子関数が定義されていないクラスとその他の型では、名前探索を行って記憶域の確保に用いるnewないしnew[]演算子関数を決定する。このため、大域名前空間にはデフォルトのnewとnew[]演算子関数が定義されており、標準C++ライブラリの中で唯一の大域名前空間で定義された関数となっており、ヘッダ<new>に宣言が置かれている。一方で、この大域名前空間のnew、new[]演算子関数はプログラム内で定義を与えることも可能で、そうした場合、処理系の用意した定義に代わってプログラム内の定義が用いられる (X3014 17.4.3.4)。

::new Tのように、new、new[]に::を前置すると、大域名前空間のnew、new[]演算子関数で記憶域の確保することを強制できる (X3014 5.3.4 9)。この場合、解放には::delete、::delete[]を使用する必要がある。

ちなみに、初期のC++では記憶域の確保と初期化が分離しておらず、クラス型に対するnewで独自の記憶域の確保方法を用いるには、コンストラクタ内で、thisへ代入を行うという構文を用いていた (D&E 3.9)。

既定のnew演算子関数[編集]

大域名前空間のnew及びnew[]演算子関数がプログラムによって定義されなかった場合に用いられる既定の実装は、次のような動作を行う (X3014 18.4.1.1)。

  • 次の内容のループを行う。
    1. 何らかの方法で記憶域確保を試みる。
      • 成功すればそれを返すことで関数を抜ける。
    2. 失敗した場合、newハンドラが登録されているか確認する。
      • 登録されていたら、そのnewハンドラを呼び出す。
      • newハンドラが登録されていなければ、std::bad_alloc型のインスタンスが例外として投げられる。

配置new[編集]

配置new (プレースメントnew, placement new)は、new演算子からnew演算子関数へ引数を与えられる機能である。当初、インスタンスを特定のメモリアドレスに「配置」するための機能ということで配置newと命名された。後に配置に限らず様々な使い道に応用できることが明らかとなったものの、今でも慣習的に配置newと呼ばれる。

例えばヘッダ<new>には、通常のnew、new[]演算子関数のほか、次のようなnew、new[]演算子関数が定義されている (X3014 18.4)。

void* operator new(std::size_t, void*) throw();
void* operator new[](std::size_t, void*) throw();
void* operator new(std::size_t, std::nothrow_t) throw();
void* operator new[](std::size_t, std::nothrow_t) throw();

上の2つは引数に与えられたポインタをそのままnew演算子関数の戻り値とするもので、当初の配置newの目論見どおり指定したメモリアドレスにオブジェクトを配置するために使用できる。

class Hoge {/* ... */};
 
void *p = std::malloc(sizeof (Hoge));
Hoge *obj = new(p) Hoge;
 
//objを使う
 
obj->~Hoge();
std::free(p);

下の2つは記憶域が確保できなかったときに、例外を投げない代わりにヌルポインタを返すnewである(なお、newハンドラは呼ばれる)。std::nothrow_t型のインスタンスとしてstd::nothrowが定義されており、次のように使用する。

int* p = new(std::nothrow) int;
delete p;

delete及びdelete[]演算子は、std::nothrow_tを引数に取るnewが返す記憶域も解放できると定められている。また、std::nothrow_tを引数に取るものも、そうでない(nothrow_tを引数に取らない配置newでないnew演算子関数)もの同様にプログラム内で定義可能とされている (X3014 18.4.1.1 6)。

new及びnew[]演算子を使用した際、コンストラクタが例外を投げると、コンパイラは引数の対応するdeleteないしdelete[]演算子関数で記憶域を解放しようとする。そのため、new及びnew[]演算子で独自の記憶域確保を行う場合、対応するdelete、delete[]演算子を用意すべきであるとされる[1]

class MyAllocator;
 
class Foo
{
    static void* operator new(std::size_t, MyAllocator);
    static void* operator new[](std::size_t, MyAllocator);
    static void operator delete(void*, MyAllocator);
    static void operator delete[](void*, MyAllocator);
};

エラー処理[編集]

newやnew[]が記憶域を確保できなかった場合、一般的には例外std::bad_alloc型(またはその派生クラス)のインスタンスが投げられると理解されている。

newハンドラ[編集]

newハンドラは、既定のnew及びnew[]演算子関数で記憶域確保に失敗した場合に呼ばれるコールバック関数であり、std::set_new_handlerで登録できる。当時まだ例外処理がなかったC++で、記憶域確保の失敗をまとめて取り扱うために導入された。

newハンドラでは、例外を投げたり、プログラムを終了させたりするなどのほか、何らかの方法で記憶域に空きを作ることで、new、new[]演算子関数の記憶域確保を成功へ導かせることも可能である。

C++/CLI[編集]

C++/CLIでは、C++のnew演算子のほかに、マネージヒープから記憶域を確保するgcnew演算子が存在する。

System::Object^ o = gcnew System::Object;

gcnewには、配置構文は存在しない。

配列構文も存在せず、CLI配列(マネージ配列)の作成には、array型を用いる。

array<int>^ a1 = gcnew array<int>(10); //要素数10の1次元配列
array<int, 2>^ a2 = gcnew array<int, 2>(3, 4); //3×4の2次元配列

ただし、gcnewには配列初期化の構文が存在する。

array<int>^ a1 = gcnew array<int>(4) {0, 1, 2, 3};
array<int>^ a2 = gcnew array<int> {0, 1, 2, 3}; //上と同じ。初期化子から要素数が算出される。
 
array<int, 2>^ a3 = gcnew array<int, 2> {{0, 1}, {2, 3}}; //多次元配列の例

C#[編集]

C#のnew演算子は、インスタンスを生成・初期化するという意味を持っており、参照型を対象とする場合はヒープから記憶域を確保するが、構造体を対象とする場合は単に一時的なインスタンス(これはヒープ上に生成されるのではない)を初期化するだけである。

下のコードの場合、少なくとも概念上は、一時的なPoint型のインスタンスが生成され、それがptへ複写されているという扱いである。

using System.Drawing;
//Pointは値型なので、ptはスタック上に存在する。
Point pt = new Point();

Visual Basic[編集]

Visual Basicには、キーワードNewが存在し、COMのクラスのインスタンス作成に用いる。

'MSXML2が参照設定されてあるものとする。
Dim xd As MSXML2.DOMDocument
Set xd = New MSXML2.DOMDocument

また、次のように変数の宣言と同時にインスタンスを作成し変数を初期化させることも可能である。

Dim xd As New MSXML2.DOMDocument

ただし、この2つのコード例は必ずしも同じ意味を持つとは限らない。

Visual Basic .NET[編集]

Visual Basic .NETのNewは、概ねVisual Basicの構文を踏襲している。しかし、CLIクラスを対象にする点が異なる。

Dim o1 As System.Object
o1 = New System.Object()
 
Dim o2 As New System.Object()

また、配列の初期化も可能となった。

Dim a As Integer()
a = New Integer() {0, 1, 2}

他の手法[編集]

オブジェクト指向の言語では、何かしらの方法によりオブジェクトを生成できるようにする必要があるが、その手段が演算子である必然性はない。例えば、C++では、参照やポインタとしてでなく宣言されたオブジェクト型の変数は、暗黙のうちにオブジェクトを生成し、自動的に初期化される。また、Objective-CRuby[2]のように、オブジェクトの生成をクラスメソッドにより行う言語もあるほか、オブジェクトの生成をファクトリメソッドに落としこんで、継承により上書き可能な形とすることも行われる。

参考文献[編集]

関連項目[編集]

脚注[編集]

  1. ^ Effective C++ 原著第3版 52項
  2. ^ instance method Class#new Ruby 1.9.2 リファレンスマニュアル、2013年11月22日閲覧。