newとdelete
メモリの動的確保
C言語ではメモリの確保にはmalloc
関数などを使用していました。
C++ではメモリの動的確保が構文に組み込まれ、簡単にできるようになっています。
new演算子
new
演算子は、指定したデータ型を格納するメモリを確保し、そのポインタを返します。
#include <iostream>
int main()
{
int buf = 100;
//これはできない
//int nums[buf];
int *nums = new int[buf];
for (size_t i = 0; i < buf; i++)
{
nums[i] = i;
}
std::cout << nums[0] << std::endl;
std::cout << nums[50] << std::endl;
std::cout << nums[99] << std::endl;
//newしたものはdeleteする
delete[] nums;
std::cin.get();
}
8行目のように、通常の配列では変数を配列のサイズに指定することはできません。
new
演算子を使用すれば、変数の値でメモリを確保することができます。
new
演算子で返ってくるのはポインタですから、受け取り側もポインタ変数にします。
受け取った後は通常の配列と同じように使用することができます。
サンプルコードでは配列を生成していますが、単体の変数を生成する場合はnew int()
と、データ型に続いて丸括弧を記述します。
引数が空の場合はそのデータ型の初期値、引数に値を指定すればその値で初期化されます。
//単体(値は0)
int *num = new int();
//単体(値は10)
int *num = new int(10);
//配列(要素数10、各要素の値は不定値)
int *nums = new int[10];
メモリ確保に失敗した場合
new
演算子はメモリの確保に失敗すると例外が発生します。
例外は、何もしなければそこでプログラムが停止します。
(適切に処理することで停止させずにプログラムを続行させることはできます)
失敗時に例外を発生させずに、C言語のmalloc
関数のようにNULLポインタ(nullptr
)を返すようにするにはstd::nothrow
をnew
演算子に指定します。
int* num = new(std::nothrow) int();
if (!num) {
//メモリ確保に失敗
}
delete num;
delete演算子
動的確保したメモリは、必要がなくなれば解放する必要があります。
メモリを解放するにはdelete
演算子を使用します。
new
で確保したメモリは、必ずdelete
で解放するのがルールです。
delete
しないままだとメモリが確保されたままになってしまい、メモリリークの原因となります。
(メモリリーク=解放できないメモリ領域が発生すること)
C言語のmalloc
関数と同じく、プログラムの終了と同時に確保したメモリは自動的に解放されるので、サンプルコードのような単純なものであればdelete
しなくても問題は発生しません。
delete
演算子の書き方には少し注意点があります。
//単体の場合
int *a = new int();
delete a;
//配列形式でnewした場合は
//配列形式でdeleteする
int *b = new int[1];
delete[] b;
int *c = new int[10];
delete[] c;
配列としてメモリを確保した場合にはdelete[] ポインタ変数名
という形式にする必要があります。
配列形式に対してdelete
を実行した場合、およびオブジェクト(非配列形式)に対してdelete[]
を実行した場合の動作は未定義で、不具合が生じる可能性があります。
なお、delete
演算子は値を返しません。
また、NULLポインタ(nullptr
)に対するdelete
は安全です。
メモリを確保しポインタを返す関数
delete
するまではメモリ上に領域は確保されたままですから、関数内でメモリを確保してそのポインタを返すことができます。
これはC言語のmalloc
関数で確保したメモリを「自動解放されない領域」として使ったのと同じです。
#include <iostream>
//指定のサイズの配列をポインタで返す関数
int *createArray(int size, int n = 0)
{
int *arr = new int[size];
for (size_t i = 0; i < size; i++)
{
arr[i] = n;
}
return arr;
}
int main()
{
int *nums = createArray(100, 1);
std::cout << nums[0] << std::endl;
std::cout << nums[50] << std::endl;
std::cout << nums[99] << std::endl;
delete[] nums;
std::cin.get();
}
関数内で宣言した配列は関数を抜けた時に寿命が尽きるので、関数外からアクセスすることはできなくなります。
しかしnew
演算子でメモリを確保した場合は勝手に消去されないので、戻り値でポインタを受け取るようにすれば外部からでもアクセスが可能になります。
ただし、そのメモリは後で自分で解放する必要があります。
メモリ管理の事を考えなければならないのが面倒なので、C++ではこういった使い方はあまり見かけません。
代わりにvectorなどのコンテナ型や、自作のクラスを使用します。
(クラスについては別途詳しく説明します)
new、deleteの注意点
new
演算子、delete
演算子はほとんどmalloc
関数、free
関数の代用として使用することができます。
しかしmalloc
関数との互換性があるわけではありません。
malloc
関数で確保したメモリをdelete
演算子で解放しようとしたり、new
演算子で確保したメモリをfree
関数で解放しようとしたりしてはいけません。