こんにちは、ナナです。
プログラムにおける代表的なメモリは「スタックメモリ」「静的メモリ」「動的メモリ(ヒープメモリ)」です。
この「動的メモリ」を利用することで、メモリ領域の確保と解放のタイミングをエンジニアがコントロールすることができるようになります。
C言語における動的メモリといえば「malloc関数」と「free関数」によって確保/解放を行いましたが、C++では動的メモリを確保/解放するための新しい「演算子」が用意されました。
それが、「new演算子」と「delete演算子」です。
new/delete演算子を使った動的メモリの確保と解放
「動的メモリ」って言えば、メモリの銀行みたいなものでしたわね。知ってるわよ、「malloc関数」に頼めばいいんでしょ!
それでは1ギガバイトのメモリを貸し出していただけるかしらっ!
メモリは、お金と同じで借りたら必ず返さなきゃいけないからね。気を付けるんだよ。
C言語では「malloc関数」を使いましたが、C++では別の方法で動的メモリを借りることができます。その方法を紹介しようね。
動的メモリを忘れてしまっている人は『C言語 動的メモリ【ヒープメモリの使い方と獲得する方法】』を事前に読んでおきましょう。
new/delete演算子の役割と使い方の基本
まずは各演算子の役割を明確にしておきましょう。
演算子 | 役割 |
---|---|
new | 指定したデータ型の動的メモリを確保する。 ポインタでメモリ番地を受け取ることができる。 |
delete | newで確保した動的メモリを解放する。 |
new演算子を使えば、int型のような組み込み型はもちろん、構造体・クラスといったデータ型の動的メモリを確保することができます。
#include <stdio.h>
int main()
{
// 動的メモリの確保
int * pNum1 = new int; // 初期化なし
short * pNum2 = new short(50); // 初期化あり
*pNum1 = 100;
printf("Num1:%d\n", *pNum1);
printf("Num2:%d\n", *pNum2);
// 動的メモリの解放
delete pNum1;
delete pNum2;
return 0;
}
new演算子を使うと、確保された動的メモリへのポインタが取得できます。
newで確保した動的メモリは、使い終わったタイミングでdeleteによる解放が必要となります。
C++でもmalloc/free関数を使うことはできますが、new/delete演算子を使うのが基本となります。理由は後ほど説明しましょう!
new[]/delete[]演算子で「配列」を確保・解放する方法
連続したメモリ領域である「配列」をnew/deleteで確保・解放するときには書き方に注意が必要です。
int main()
{
short * pArray = new short[5]; // 配列の動的メモリ確保
for (int i=0; i < 5; i++)
{
pArray[i] = i;
}
delete[] pArray; // 配列の動的メモリ解放
return 0;
}
このように配列領域を動的メモリに確保する際は、new/deleteに[]を付けて行います。
演算子 | 役割 |
---|---|
new[] | []内に指定した要素数分の動的メモリを確保する。 ポインタで配列の先頭番地を受け取ることができる。 |
delete[] | new[]で確保した動的メモリを解放する。 |
C++においてnewとnew[]は異なる別の演算子として認識されています。見た目はほぼ一緒ですが、別のものだと覚えておきましょう。
delete[]演算子に関しては書き方に違和感を感じると思いますが、「演算子名+ポインタ」と考えれば、delete も delete[]も同じ書き方なのです。
クラスオブジェクトに対するnew/deleteの使い方
組み込み型でもクラス型でもnew/deleteの扱い方は一緒です。次のプログラムは「POS」クラスを動的メモリに確保する例です。
#include <stdio.h>
class POS
{
public:
int x;
int y;
};
int main()
{
// オブジェクトの確保と解放
POS * pPos = new POS;
delete pPos;
// オブジェクト配列の確保と解放
POS * pPosArray = new POS[5];
delete[] pPosArray;
return 0;
}
注意:C++ではnew/deleteを使え!malloc/freeは極力使うな
C++では動的メモリを確保・解放する方法が2つあるってことでいいのよね?
どっちを使ってもいいのかしら?わたくしの一存で決めさせていただくわよっ!
C++では、従来のC言語から利用できる「malloc/fee関数」と、新しく導入された「new/delete演算子」の2つが利用できます。
しかし、C++においては基本的に「new/delete演算子」を使用すると覚えておきましょう!
C++において動的メモリの確保・解放を行うのは基本的に「new/delete演算子」を使います。その理由を知っておきましょう。
malloc関数でクラスオブジェクトのメモリを確保してはいけない理由
実は「new/delete演算子」には、動的メモリを確保・解放する以外の別の仕事が用意されています。
それは、クラスオブジェクトの「コンストラクタ」「デストラクタ」を呼び出すという仕事です。
次のプログラムは、クラスオブジェクトを「new/delete」と「malloc/free」でそれぞれ生成・解体したときの実行例です。
#include <stdio.h>
class POS
{
public:
int x;
int y;
POS();
~POS();
};
POS::POS()
{
x = 0;
y = 0;
printf("コンストラクタ\n");
}
POS::~POS()
{
printf("デストラクタ\n");
}
int main()
{
POS* pos = new POS;
delete pos;
return 0;
}
コンストラクタ
デストラクタ
#include <stdio.h>
#include <stdlib.h>
class POS
{
public:
int x;
int y;
POS();
~POS();
};
POS::POS()
{
x = 0;
y = 0;
printf("コンストラクタ\n");
}
POS::~POS()
{
printf("デストラクタ\n");
}
int main()
{
POS* pos = (POS*)malloc(sizeof(POS));
free(pos);
return 0;
}
このように、malloc/free関数では動的メモリの確保・解放はしてくれますが、「コンストラクタ」「デストラクタ」を呼び出してくれません。
これは、
malloc/free関数ではオブジェクトの生成と解体を適切にできない
ことを示します。
C++に「new/delete」という新しい演算子が生まれた理由は、クラスオブジェクトを動的メモリに正しく生成・解体する必要性があったからなのです。
「new/delete演算子」と「malloc/free関数」は混ぜるな危険!
「new/delete演算子」と「malloc/free関数」は、動的メモリを確保・解放するという目的は一緒ですが、実現している方法が異なります。
つまり、new演算子で確保したメモリをfree関数で解放する、malloc関数で確保したメモリをdelete演算子で解放する、ということはできません。
次の例はnew演算子とfree関数を組み合わせたものですが、このようなプログラムは書いてはなりません。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* pNum = new int; // 動的メモリの確保
*pNum = 100;
free(pNum); // 動的メモリの解放
return 0;
}
ビルドエラーは発生せず動かすことはできますが、結果は不定であり何が起こるかはわかりません。
このように混在させることは非常に危険であるということです。malloc/free関数でなければならない時以外は、new/deleteを使うように統一しておきましょう。
Q&A:new/deleteに関するよくある質問
Q:配列を動的メモリで確保したときの初期化方法は?
int型の配列を動的メモリに確保した場合は、確保と同時に初期化はどうしたらできるの?教えなさいっ!
うーん、これは制限付きですが可能です。その制限とは「0」で初期化することです。それ以外の値では初期化できないので、確保後に自分で入れるしかないですね。
new[]演算子を使って、組み込み型を動的メモリの配列で確保する場合は、次の書き方で0初期化することは可能です。
具体的なプログラムで示しましょう。
#include <stdio.h>
int main()
{
int * pArray = new int[5](); // 配列の確保と0初期化
for (int i = 0; i < 5; i++)
{
printf("pArray[%d]:%d\n", i, pArray[i]);
}
delete[] pArray;
return 0;
}
pArray[0]:0
pArray[1]:0
pArray[2]:0
pArray[3]:0
pArray[4]:0
calloc関数のように確保された領域が0初期化されているのがわかりますね。
クラスオブジェクトの配列の場合は、デフォルトコンストラクタによって配列オブジェクトを初期化することになります。
Q:new[]で確保したメモリをdeleteで解放したらどうなるの?
「new/delete」と「malloc/free」を混ぜるのはダメってことだけど、new[]で配列を確保した場合に、deleteを使って解放したらどうなるのかしら?
new/deleteと、new[]/delete[]も異なる組み合わせのため、混在させて使ってはいけません。
この結果も不定であり、危険な処理となります。万が一動いたとしても、解放されるのは1つだけのオブジェクトになってメモリリークが発生します。
new/deleteと、new[]/delete[]は異なる演算子であることを知っておきましょう。この2つを混ぜてはいけません。
#include <stdio.h>
class POS
{
public:
int x;
int y;
POS();
~POS();
};
POS::POS()
{
x = 0;
y = 0;
printf("コンストラクタ\n");
}
POS::~POS()
{
printf("デストラクタ\n");
}
int main()
{
POS* pos = new POS[5]; // 配列オブジェクトをnew[]で生成
delete pos; // deleteで解放
return 0;
}
コンストラクタ
コンストラクタ
コンストラクタ
コンストラクタ
コンストラクタ
デストラクタ
コンストラクタが5回呼ばれているのに、デストラクタが1回しか呼ばれていません。
Visual Studio環境でこのプログラムを動かすと、最終的に例外が発生してプログラムが停止します。
この問題は実践的にも起きやすい不具合です。ビルドエラーにならないため注意が必要です。