C++入門|new演算子とdelete演算子

newはヒープ領域に修行カプセルを召喚する合図、deleteは使い終えたカプセルを消去する後片付け。C++では、生成したメモリは自分の手で責任を持って解放します。

C++では、クラスからインスタンスを生成するとき、これまで次のような書き方をしてきました。

KiTrainingCapsule capsule;

このように関数の中で普通に宣言したインスタンスは、主にスタック領域に作られます。

スタック領域に作られたインスタンスは、その関数の処理が終わると自動的に消えます。
つまり、main関数の中で作ったインスタンスなら、main関数が終わると自動的に消去されます。

これはとても便利ですが、いつ生成して、いつ消すかを細かく自分で決めたい場面もあります。

たとえば、ドラゴンボール風に考えてみましょう。

近未来の修行司令室で、必要なタイミングだけ重力修行カプセルを起動したいとします。
修行が終わったら、main関数の終了を待たずに、すぐにそのカプセルを消したい場合もあります。

このように、自由なタイミングでインスタンスを生成し、自由なタイミングで消去したいときに使うのが、new演算子delete演算子です。

演算子役割ドラゴンボール風のイメージ
newヒープ領域にインスタンスを生成する修行カプセルを任意のタイミングで召喚する
deletenewで生成したインスタンスを消去する使い終えた修行カプセルを片付ける

newで作ったインスタンスは、ヒープ領域に作られます。
ヒープ領域に作られたインスタンスは、スタック領域のように関数終了で自動的に消えるとは限りません。

そのため、使い終わったらdeleteで消去する必要があります。

KiTrainingCapsule* pCapsule = new KiTrainingCapsule();
delete pCapsule;

この流れを理解すると、C++でメモリを扱う感覚がぐっと深まります。

図:newでヒープ領域に修行カプセルを生成する

この図が示していること

この図では、new演算子によってヒープ領域にKiTrainingCapsuleインスタンスが生成され、そのアドレスをポインタ変数pCapsuleが記憶する流れを表しています。

pCapsuleそのものはmain.cppの中で使うポインタ変数です。
実際のインスタンスはヒープ領域に作られます。

使い終わったら、delete pCapsuleでヒープ領域のインスタンスを消去します。

メモリの4領域

C++のプログラムを実行すると、OSはそのプログラムのためにメモリ領域を用意します。

このメモリ領域は、大きく次の4つに分けて考えることができます。

領域置かれるものイメージ
プログラム領域実行されるプログラム本体修行司令書そのもの
静的領域グローバル変数、static変数最初から最後まで残る固定設備
ヒープ領域newで動的に確保したメモリ必要なときに出現させる修行カプセル置き場
スタック領域ローカル変数など関数の中だけ使う一時的な作業台

ドラゴンボール風にたとえると、プログラム領域は修行司令室に登録された命令書です。

静的領域は、プログラム開始から終了までずっと存在する固定設備です。
スタック領域は、関数が動いている間だけ使う一時的な作業台です。
そしてヒープ領域は、必要なタイミングで修行カプセルを出したり消したりできる自由な空間です。

スタック領域に作られるインスタンス

これまでよく使ってきたインスタンス生成は、次のような形でした。

KiTrainingCapsule capsule;

このように関数の中で普通に宣言したインスタンスは、スタック領域に作られます。

スタック領域にあるインスタンスは、その関数の処理が終わると自動的に消えます。

たとえば、main関数の中でcapsuleを作った場合、main関数が終わるときにcapsuleも消えます。

int main(int argc, char** argv) {
    KiTrainingCapsule capsule;

    capsule.setPower(400);
    capsule.train(1.5);

    return 0;
}

この場合、capsuleはmain関数の終了時に自動的に消去されます。
その直前にデストラクタが呼び出されます。

つまり、スタック領域のインスタンスは、生成と消去のタイミングが関数の範囲に強く結びついています。

ヒープ領域に作られるインスタンス

一方、new演算子を使うと、インスタンスをヒープ領域に作れます。

KiTrainingCapsule* pCapsule = new KiTrainingCapsule();

この書き方では、KiTrainingCapsuleインスタンスがヒープ領域に生成されます。
そして、そのインスタンスのアドレスがpCapsuleに入ります。

pCapsuleはポインタ変数です。

書き方意味
KiTrainingCapsule*KiTrainingCapsule型のインスタンスを指すポインタ
pCapsuleポインタ変数名
new KiTrainingCapsule()ヒープ領域にKiTrainingCapsuleインスタンスを生成する
pCapsule = new KiTrainingCapsule();生成したインスタンスのアドレスをpCapsuleに入れる

ヒープ領域に作ったインスタンスは、deleteで消去するまで残ります。

delete pCapsule;

このdeleteを忘れると、使い終わったメモリが解放されずに残ってしまいます。

C++では、newで生成したものは、基本的にdeleteで消す必要があります。

スタックとヒープの違い

スタック領域とヒープ領域の違いを整理すると、次のようになります。

比較項目スタック領域ヒープ領域
生成方法KiTrainingCapsule capsule;new KiTrainingCapsule()
消去方法関数終了時に自動で消えるdeleteで明示的に消す
扱い方変数名で直接使うポインタを通して使う
メンバアクセスcapsule.train(1.5);pCapsule->train(1.5);
向いている場面短い範囲で使う一時的なインスタンス自由なタイミングで生成・消去したいインスタンス

ドラゴンボール風に言えば、スタック領域は「関数内で一時的に使う訓練台」です。
ヒープ領域は「必要なときに呼び出して、使い終わったら自分で片付ける修行カプセル置き場」です。

図:スタック領域とヒープ領域の違い

この図が示していること

この図では、スタック領域とヒープ領域の違いを表しています。

スタック領域に作ったインスタンスは、関数が終わると自動的に消えます。
ヒープ領域に作ったインスタンスは、newで生成し、deleteで消去します。

また、スタック上のインスタンスはドット演算子でメンバへアクセスします。
ヒープ上のインスタンスをポインタで扱う場合は、アロー演算子を使います。

今回作成するサンプルプログラム

今回のサンプルでは、KiTrainingCapsuleクラスを使います。

KiTrainingCapsuleは、修行カプセルを表すクラスです。
コンストラクタでは起動メッセージを表示し、デストラクタでは終了メッセージを表示します。

newでインスタンスを生成したタイミングで、コンストラクタが呼び出されます。
deleteでインスタンスを消去したタイミングで、デストラクタが呼び出されます。

メンバ内容
KiTrainingCapsuleコンストラクタ。修行カプセル起動時の処理
~KiTrainingCapsuleデストラクタ。修行カプセル終了時の処理
setPower気の出力を設定する
getTotalEnergy総蓄積エネルギーを取得する
train指定した時間だけ修行する

ファイル構成は、次の3つです。

ファイル役割
ki_training_capsule.hKiTrainingCapsuleクラスの宣言
ki_training_capsule.cppコンストラクタ、デストラクタ、メンバ関数の定義
main.cppnewとdeleteを使ってインスタンスを生成・消去する

KiTrainingCapsuleクラスの宣言

プロジェクト/ファイル名: Chap4_02/ki_training_capsule.h

#ifndef _KI_TRAINING_CAPSULE_H_
#define _KI_TRAINING_CAPSULE_H_

class KiTrainingCapsule {
public:
    // コンストラクタ
    KiTrainingCapsule();

    // デストラクタ
    ~KiTrainingCapsule();

    // 気の出力を設定する
    void setPower(double power);

    // 気の出力を取得する
    double getPower();

    // 総蓄積エネルギーを取得する
    double getTotalEnergy();

    // 指定した時間だけ修行する
    void train(double hour);

private:
    // 現在の気の出力
    double m_power;

    // 総蓄積エネルギー
    double m_totalEnergy;
};

#endif // _KI_TRAINING_CAPSULE_H_

このヘッダーファイルでは、KiTrainingCapsuleクラスを宣言しています。

注目したいのは、コンストラクタとデストラクタです。

KiTrainingCapsule();
~KiTrainingCapsule();

KiTrainingCapsule()は、インスタンスが生成されたときに呼び出されます。
~KiTrainingCapsule()は、インスタンスが消去される直前に呼び出されます。

newでインスタンスを生成したときにも、コンストラクタは呼び出されます。
deleteでインスタンスを消去したときにも、デストラクタは呼び出されます。

KiTrainingCapsuleクラスの定義

プロジェクト/ファイル名: Chap4_02/ki_training_capsule.cpp

#include "ki_training_capsule.h"
#include <iostream>

using namespace std;

// コンストラクタ
KiTrainingCapsule::KiTrainingCapsule() : m_power(0.0), m_totalEnergy(0.0) {
    cout << "修行カプセルをヒープ領域に起動しました。" << endl;
}

// デストラクタ
KiTrainingCapsule::~KiTrainingCapsule() {
    cout << "修行カプセルをヒープ領域から消去しました。" << endl;
}

// 気の出力を設定する
void KiTrainingCapsule::setPower(double power) {
    m_power = power;
}

// 気の出力を取得する
double KiTrainingCapsule::getPower() {
    return m_power;
}

// 総蓄積エネルギーを取得する
double KiTrainingCapsule::getTotalEnergy() {
    return m_totalEnergy;
}

// 指定した時間だけ修行する
void KiTrainingCapsule::train(double hour) {
    cout << "気の出力" << m_power << "で" << hour << "時間修行" << endl;
    cout << m_power * hour << "ポイントの気を蓄積しました。" << endl;

    // 修行で蓄積した気を総蓄積エネルギーに加算する
    m_totalEnergy += m_power * hour;
}

このファイルでは、KiTrainingCapsuleクラスのメンバ関数を定義しています。

コンストラクタでは、m_powerとm_totalEnergyを0.0で初期化しています。

KiTrainingCapsule::KiTrainingCapsule() : m_power(0.0), m_totalEnergy(0.0) {
    cout << "修行カプセルをヒープ領域に起動しました。" << endl;
}

new KiTrainingCapsule()が実行されると、このコンストラクタが呼び出されます。

デストラクタでは、消去メッセージを表示しています。

KiTrainingCapsule::~KiTrainingCapsule() {
    cout << "修行カプセルをヒープ領域から消去しました。" << endl;
}

delete pCapsuleが実行されると、このデストラクタが呼び出されます。

newとdeleteを使うmain.cpp

プロジェクト/ファイル名: Chap4_02/main.cpp

#include <iostream>
#include "ki_training_capsule.h"

using namespace std;

int main(int argc, char** argv) {
    // 修行カプセルを指すポインタ変数を用意する
    KiTrainingCapsule* pCapsule = NULL;

    // ヒープ領域に修行カプセルのインスタンスを生成する
    pCapsule = new KiTrainingCapsule();

    // 気の出力を設定する
    pCapsule->setPower(400);

    // 指定した時間だけ修行する
    pCapsule->train(1.5);

    // 気の出力を再設定する
    pCapsule->setPower(650);

    // 指定した時間だけ修行する
    pCapsule->train(2.0);

    // 総蓄積エネルギーを表示する
    cout << "総蓄積エネルギー:" << pCapsule->getTotalEnergy() << "ポイント" << endl;

    // ヒープ領域のインスタンスを消去する
    delete pCapsule;

    // 消去が終わったことを表示する
    cout << "修行カプセルの消去処理が終了しました。" << endl;

    // プログラムが正常に終わったことを示す
    return 0;
}

実行結果は次のようになります。

修行カプセルをヒープ領域に起動しました。
気の出力400で1.5時間修行
600ポイントの気を蓄積しました。
気の出力650で2時間修行
1300ポイントの気を蓄積しました。
総蓄積エネルギー:1900ポイント
修行カプセルをヒープ領域から消去しました。
修行カプセルの消去処理が終了しました。

この実行結果を見ると、newとdeleteのタイミングが分かります。

最初の次の表示は、newでインスタンスを生成したときに呼び出されたコンストラクタによるものです。

修行カプセルをヒープ領域に起動しました。

そして、次の表示は、deleteでインスタンスを消去したときに呼び出されたデストラクタによるものです。

修行カプセルをヒープ領域から消去しました。

main関数の終了を待たずに、delete pCapsuleの位置でインスタンスを消去できていることが分かります。

ポインタ変数をNULLで初期化する

main.cppの最初では、ポインタ変数をNULLで初期化しています。

KiTrainingCapsule* pCapsule = NULL;

pCapsuleは、KiTrainingCapsuleインスタンスのアドレスを記憶するためのポインタ変数です。

まだインスタンスを指していない段階なので、NULLを入れています。

書き方意味
KiTrainingCapsule*KiTrainingCapsule型を指すポインタ
pCapsuleポインタ変数名
NULLまだ何も指していない状態

ドラゴンボール風に言えば、pCapsuleは修行カプセルの位置を記録する司令室のレーダーです。

最初はまだカプセルを召喚していないので、何も指していない状態です。
newを実行すると、ヒープ領域にカプセルが出現し、その場所をpCapsuleが記憶します。

new演算子でインスタンスを生成する

次の行で、new演算子を使っています。

pCapsule = new KiTrainingCapsule();

この処理では、ヒープ領域にKiTrainingCapsuleインスタンスを生成しています。

そして、生成されたインスタンスのアドレスがpCapsuleに代入されます。

このタイミングで、KiTrainingCapsuleクラスのコンストラクタが呼び出されます。

修行カプセルをヒープ領域に起動しました。

newの基本的な書式は、次のようになります。

new コンストラクタ名()

今回の場合は、次のようになります。

new KiTrainingCapsule()

KiTrainingCapsule()はコンストラクタです。
newは、そのコンストラクタを呼び出して、ヒープ領域にインスタンスを生成します。

ポインタからメンバへアクセスする

newで生成したインスタンスは、ポインタ変数pCapsuleを通して操作します。

ポインタ変数からメンバ関数を呼び出すときは、アロー演算子を使います。

pCapsule->setPower(400);
pCapsule->train(1.5);
pCapsule->getTotalEnergy();

スタック領域に作ったインスタンスでは、ドット演算子を使いました。

capsule.setPower(400);

しかし、ポインタ変数の場合はドット演算子ではなく、アロー演算子を使います。

対象書き方
通常のインスタンス.capsule.setPower(400);
ポインタ変数->pCapsule->setPower(400);

pCapsuleはインスタンスそのものではなく、インスタンスのアドレスを持っています。

そのため、pCapsuleが指している先のインスタンスのメンバを使う、という意味でアロー演算子を使います。

delete演算子でインスタンスを消去する

修行が終わったら、delete演算子でインスタンスを消去します。

delete pCapsule;

この処理によって、newでヒープ領域に生成したKiTrainingCapsuleインスタンスが消去されます。

このタイミングで、デストラクタが呼び出されます。

修行カプセルをヒープ領域から消去しました。

deleteの基本的な書式は、次のようになります。

delete ポインタ変数

今回の場合は、次のようになります。

delete pCapsule;

newで生成したインスタンスは、自動では消えません。
そのため、使い終わったら必ずdeleteで消去します。

ドラゴンボール風に言えば、修行カプセルをヒープ領域に召喚したら、修行終了後にきちんと格納庫へ戻す必要があるということです。

newとdeleteの処理の流れ

今回の処理の流れを整理すると、次のようになります。

手順処理内容
1KiTrainingCapsule* pCapsule = NULL;ポインタ変数を用意する
2pCapsule = new KiTrainingCapsule();ヒープ領域にインスタンスを生成する
3コンストラクタ実行m_powerとm_totalEnergyを0.0に初期化する
4pCapsule->setPower(400);気の出力を400に設定する
5pCapsule->train(1.5);600ポイント蓄積する
6pCapsule->setPower(650);気の出力を650に設定する
7pCapsule->train(2.0);1300ポイント蓄積する
8pCapsule->getTotalEnergy();1900ポイントを取得する
9delete pCapsule;インスタンスを消去する
10デストラクタ実行終了処理が行われる

newで生成したタイミングでコンストラクタが呼び出されます。
deleteで消去したタイミングでデストラクタが呼び出されます。

図:newからdeleteまでの流れ

この図が示していること

この図では、new演算子でインスタンスを生成し、ポインタ変数pCapsuleを使って操作し、最後にdelete演算子で消去する流れを表しています。

new KiTrainingCapsule()でヒープ領域に修行カプセルが生成されます。
pCapsule->setPowerやpCapsule->trainで、その修行カプセルを操作します。
delete pCapsuleで修行カプセルが消去され、デストラクタが呼び出されます。

ドット演算子とアロー演算子の違い

C++では、インスタンスを直接扱う場合と、ポインタで扱う場合で、メンバへのアクセス方法が変わります。

スタック領域のインスタンスを直接使う場合は、ドット演算子を使います。

KiTrainingCapsule capsule;
capsule.setPower(400);
capsule.train(1.5);

一方、newで生成したインスタンスをポインタで扱う場合は、アロー演算子を使います。

KiTrainingCapsule* pCapsule = new KiTrainingCapsule();
pCapsule->setPower(400);
pCapsule->train(1.5);
delete pCapsule;

表にすると、次のようになります。

使い方インスタンス生成メンバアクセス
直接扱うKiTrainingCapsule capsule;capsule.setPower(400);
ポインタで扱うKiTrainingCapsule* pCapsule = new KiTrainingCapsule();pCapsule->setPower(400);

アロー演算子は、ポインタが指している先のインスタンスにアクセスするための記号です。

main関数の終了前に消去できる

スタック領域に作ったインスタンスは、関数が終わるときに自動的に消えます。

一方、newで作ったインスタンスは、deleteを書いたタイミングで消せます。

今回のサンプルでは、次の位置でインスタンスを消去しています。

delete pCapsule;
cout << "修行カプセルの消去処理が終了しました。" << endl;

実行結果を見ると、delete pCapsuleのタイミングでデストラクタのメッセージが表示され、そのあとに消去完了メッセージが表示されます。

修行カプセルをヒープ領域から消去しました。
修行カプセルの消去処理が終了しました。

つまり、main関数が完全に終了する前に、ヒープ領域のインスタンスを消去できています。

これは、newとdeleteを使う大きな特徴です。

newで作ったらdeleteする

C++では、newで生成したインスタンスは、使い終わったらdeleteで消去する必要があります。

KiTrainingCapsule* pCapsule = new KiTrainingCapsule();

// インスタンスを使う処理

delete pCapsule;

newだけを書いてdeleteを書かないと、ヒープ領域に確保したメモリが残り続けてしまいます。

これは、修行が終わったのに修行カプセルを格納庫へ戻さず、ヒープ領域に置きっぱなしにするようなものです。

プログラムが大きくなると、こうした消し忘れが問題になります。

そのため、newとdeleteはセットで考えることが大切です。

操作役割
newヒープ領域にインスタンスを生成する
deletenewで生成したインスタンスを消去する

new演算子とdelete演算子で押さえたいポイント

new演算子とdelete演算子では、次のポイントを押さえておきましょう。

ポイント内容
newヒープ領域にインスタンスを生成する
deletenewで生成したインスタンスを消去する
new実行時コンストラクタが呼び出される
delete実行時デストラクタが呼び出される
ポインタ変数newで生成したインスタンスのアドレスを記憶する
アロー演算子ポインタからメンバへアクセスする
delete忘れヒープ領域のメモリが解放されない原因になる

newとdeleteを使うと、スタック領域ではなくヒープ領域にインスタンスを作れます。

スタック領域のインスタンスは、関数が終わると自動的に消えます。
ヒープ領域のインスタンスは、自分でdeleteするまで残ります。

そのため、newを使うと生成と消去のタイミングを自分で制御できます。
ただし、その分だけ、deleteで消去する責任も自分にあります。

ドラゴンボール風に言えば、newは修行カプセルをヒープ領域に召喚する技です。
deleteは使い終えた修行カプセルをきちんと片付ける技です。

この2つをセットで覚えると、C++のメモリ管理がかなり理解しやすくなります。