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::nothrownew演算子に指定します。


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関数で解放しようとしたりしてはいけません。