クラスの実体が作られたときに呼び出されるのが、コンストラクタ関数であれば、クラスの実体が破棄される時に呼び出されるのがデストラクタ関数です。
デストラクタ関数は、次の特徴を持っています。
・関数名は、~クラス名 ・戻り値は無し(voidではなく無い) ・引数は渡せない |
とりあえず、コンストラクタ関数とデストラクタ関数を持つクラスを書いてみます。
中身はメッセージを表示するだけにして、呼び出されるタイミングを確認します。
<sample program cpp053-01>
#include <iostream> class Field { public: Field(); ~Field(); }; int main() { std::cout << "main関数の開始" << std::endl; Field field; std::cout << "main関数の終了" << std::endl; return 0; } Field::Field() { std::cout << "コンストラクタ関数" << std::endl; } Field::~Field() { std::cout << "デストラクタ関数" << std::endl; } |
どの順番でメッセージが表示されるか考えてみてください。
<実行結果>
main関数の開始 コンストラクタ関数 main関数の終了 デストラクタ関数 続行するには何かキーを押してください・・・
デストラクタ関数は「クラスの実体が破棄」された時に呼び出されます。
「main関数の終了」のメッセージが表示された時点では、まだクラスは破棄されていません。
return文が実行され、main関数が終了した時に、main関数のローカルスコープで実体化されたfieldもメモリから消えます。
コンストラクタ関数は初期化などを行う役割を持っていますが、デストラクタ関数は何に使うのでしょうか。
最後に実行されるので、何かの後始末に使えそうです。
必ずやらなければならない後始末とは「ファイルを閉じる」とか「確保したメモリを開放する」事などです。
newも説明しましたので、メモリの解放を題材にサンプルを作りましょう。
それでは、少しずつ作っていきます。
<sample program cpp053-02>
#include <iostream> class Field { public: private: int *m_pField; }; int main() { Field field; return 0; } |
フィールドクラスを宣言し、privateにint型のポインタ変数m_pFieldを作りました。
ここにnewを使って、メモリを割り当てたいと思います。
割り当てるサイズは定数を定義して決めたいと思いますが、#defineは使わずconstを使いましょう。
<sample program cpp053-03>
#include <iostream>
class Field {
public:
private:
const int SIZE;
int *m_pField;
};
int main()
{
Field field;
return 0;
}
|
この定数は、このクラスでしか使いませんので、privateに作りました。
定数の値はここでも代入出来ますが、メンバ変数と同じくコンストラクタ関数の初期化リストで初期値を入れます。
また、ポインタ変数m_pFieldにも初期値「NULL」を入れておきたいです。
<sample program cpp053-04>
#include <iostream> class Field { public: Field(); private: const int SIZE; int *m_pField; }; int main() { Field field; return 0; } Field::Field() : SIZE(10), m_pField(NULL) { } |
「メンバ変数の初期化」でも書いた通り、順番に気を付けて初期化してください。
メンバ変数として用意したポインタ変数に「NULL」を入れるのはとても重要な事です。
この定数を使って、メモリの確保を行いますが、コンストラクタ内では行わず、別関数を作ります。
<sample program cpp053-05>
#include <iostream> class Field { public: Field(); bool AllocateField(); private: const int SIZE; int *m_pField; }; int main() { Field field; return 0; } Field::Field() : SIZE(10), m_pField(NULL) { } bool Field::AllocateField() { m_pField = new int[SIZE]; if (!m_pField) { return false; } return true; } |
bool型の戻り値を返す、AllocateField関数を追加しました。
領域が確保出来ればtrueを、出来なければfalseを返します。
関数呼び出しを追加しましょう。
<sample program cpp053-06>
#include <iostream>
class Field {
public:
Field();
bool AllocateField();
private:
const int SIZE;
int *m_pField;
};
int main()
{
Field field;
if (!field.AllocateField()) {
return 1;
}
return 0;
}
Field::Field() : SIZE(10), m_pField(NULL)
{
}
bool Field::AllocateField()
{
m_pField = new int[SIZE];
if (!m_pField) {
return false;
}
return true;
}
|
これで領域の確保が出来ました。
※実行しないでください、メモリリークが発生します。
このフィールドを使って何かゲームなどを作ったとしましょう。
※今回は作りません。
この領域を使い終わった後で解放しなければならないのですが、デストラクタ関数があれば自動的に解放してくれます。
<sample program cpp053-07>
#include <iostream> class Field { public: Field(); ~Field(); bool AllocateField(); private: const int SIZE; int *m_pField; }; int main() { Field field; if (!field.AllocateField()) { return 1; } return 0; } Field::Field() : SIZE(10), m_pField(NULL) { } Field::~Field() { if (m_pField) { delete[] m_pField; m_pField = NULL; std::cout << "領域を解放しました。" << std::endl; } } bool Field::AllocateField() { m_pField = new int[SIZE]; if (!m_pField) { return false; } return true; } |
<実行結果>
領域を解放しました。 続行するには何かキーを押してください・・・
main関数では、解放するためのコードを書いていませんし、デストラクタ関数を呼び出す明示的なコードはありません。
main関数が終わる時に、自動的にデストラクタ関数が呼び出され、メモリ領域を解放してくれます。
領域を解放には、「安全な解放」コードを書いています。
まず、m_pFieldが「NULL」かどうかを調べています。
m_pFieldの初期値は「NULL」を入れました。
仮に、メモリの確保前に何らかの障害が発生し、main関数が終わる事になってもデストラクタ関数は実行されます。
その時に、間違って変な領域を解放しないようにするため「NULL」を初期値に入れ、解放前にチェックしているのです。
デストラクタ関数も上手く使えば非常に便利な関数ですので、色々と考えて使ってみてください。