デストラクタ

クラスの破棄時の「後片付け」

コンストラクタは「クラス生成時の準備処理」を行う機能です。
それに対して、「クラス破棄時の後処理」を行う機能がデストラクタです。


#include <iostream>

//int型の動的配列を扱うクラス
class ArrayInt
{
    int *p;    //配列の実体
    int length; //要素数

public:
    //コンストラクタ
    ArrayInt(int length = 0)
    {
        p = new int[length];
        if (p == NULL)
            this->length = 0;
        else
        {
            this->length = length;
            for (int i = 0; i < length; i++)
                p[i] = 0;
        }
    }

    //デストラクタ
    ~ArrayInt()
    {
        if (p != NULL)
            delete[] p;
    }

    //要素数を返す
    int size() { return length; }

    //indexが有効か否かを返す
    bool isValid(int index)
    {
        if (index < 0 || length <= index)
            return false;
        return true;
    }

    //indexの要素を返す
    //無効な場合は0を返す
    int get(int index)
    {
        return isValid(index) ? p[index] : 0;
    }

    //indexに値nをセットする
    void set(int index, int n)
    {
        if (isValid(index))
            p[index] = n;
    }
};

int main()
{
    ArrayInt arrInt(10);

    for (int i = 0; i < arrInt.size(); i++)
    {
        arrInt.set(i, i + 1);
    }

    std::cout << arrInt.get(0) << std::endl;
    std::cout << arrInt.get(5) << std::endl;
    std::cout << arrInt.get(9) << std::endl;

    std::cin.get();
}

自作クラスArrayIntは、int型の動的配列を扱うクラスです。
(といっても要素数の変更などはできず、最低限の機能しかありませんが)

コンストラクタでは引数に指定された要素数分のメモリをnew演算子で確保しています。
new演算子でメモリを確保した場合はdeleteするのが決まりですが、メンバ変数の初期化と同じく、忘れてしまう可能性があります。
そのような場合にはデストラクタを定義し、そこでメモリの開放処理を書きます。

デストラクタは、インスタンスが破棄される際に自動的に呼び出されます。
コンストラクタと対になる関数で、やはり特殊な関数です。

デストラクタは

  • 戻り値、引数を持たない
  • クラス名と同じ名前だが、頭に~(チルダ)を付ける

という決まりがあります。

デストラクタは基本的にコンストラクタでメモリなどのリソースを確保した場合、それを破棄する処理を書きます。
そうすることで、解放し忘れを防ぐことができます。

デストラクタを省略した場合、コンストラクタの時と同じく「何もしない」デフォルトデストラクタが自動的に生成されます。

delete演算子はデストラクタを呼び出す

これもコンストラクタと対になるものです。
new演算子がコンストラクタを呼び出し、delete演算子はデストラクタを呼び出します。


//寿命はスコープを抜けるまで
ArrayInt arr1;

//寿命は自分でdeleteするまで
ArrayInt *arr2 = new ArrayInt(10);

//何か処理...

delete arr2;
//これ以降arr2が確保したメモリは
//解放されていることが保障される

クラス内のデータは自動でdeleteされますが、クラス自身をnew演算子でインスタンス生成した場合は、そのインスタンスは自分でdeleteする必要があります。

このクラスはコピーコンストラクタを定義しておらず、そのままインスタンス同士のコピー操作を行うとデフォルトのコピーコンストラクタが使用されます。
その場合、メンバ変数のポインタの値までそのままコピーされてしまい、デストラクタ時にメモリの二重解放となってしまいます。

つまり、このサンプルのクラスはそのまま使用するとバグるかもしれない不完全なクラスです。

this

上のサンプルコードにはthisというキーワードが使われています。
(15、18行目)
これはクラス内の関数で使用できるキーワードで、自分自身のポインタを表します。

「自分自身」というのは、その関数を呼び出したインスタンスの事です。
サンプルコードではコンストラクタに使われていますから、これから生成されるインスタンス自身です。

このコンストラクタは「length」という名前で引数を受け取っています。
一方、メンバ変数にも「length」という変数があります。
これではどちらを指すのか分からなくなるので、代入される側にthisを付けて、メンバ変数であることを明示しています。
ポインタなので、メンバ変数へのアクセスにはドット演算子ではなくアロー演算子(->)を使用します。

わざわざthisを付けなくても、クラス内からはメンバ変数名を記述するだけでアクセスが可能です。
サンプルコードのような場合ならば、thisを使わなくてもどちらか一方の名前を少し変えれば解決します。
しかし、名前は重要な要素なので、できるだけ変えたくないという場合はthisを使います。

thisは他のクラスに自分を渡す場合にも使用します。
他のクラスとの連携の複雑な話になりますので今は説明を省略します。