デストラクタ

インスタンス破棄時の「後片付け」

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


#include <iostream>

//int型の動的配列を扱うクラス
class ArrayInt
{
    int* p;			//配列(ポインタで管理)
    size_t length;	//要素数

public:
    //コンストラクタ
    //length=配列サイズ
    //zeromemory=ゼロ初期化するか否か
    ArrayInt(size_t length = 0, bool zeromemory = false)
    {
		//メモリ確保
        p = new(std::nothrow)int[length];
        if (p == nullptr) {
            this->length = 0;
		}
        else
        {
            this->length = length;
            if (zeromemory) {
                for (size_t i = 0; i < length; i++)
                    p[i] = 0;
            }
        }
    }

    //デストラクタ
    ~ArrayInt()
    {
		//メモリ解放
        if (p != nullptr)
            delete[] p;
    }

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

    //indexが有効か否かを返す
    bool isValid(size_t index) { return length > index; }

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

    //indexに値nをセットする
    void set(size_t 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;
    //1
    //6
    //10

    std::cin.get();
}

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

コンストラクタでは引数に指定された要素数分のメモリをnew演算子で確保しています。
new演算子でメモリを確保した場合はdelete演算子で破棄するのが決まりですが、クラス内で確保したメモリはクラス内で破棄した方が管理がしやすいでしょう。
そこでデストラクタを定義し、そこにメモリの解放処理を書きます。

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

デストラクタは

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

という決まりがあります。
この例では関数~ArrayIntがデストラクタです。

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

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

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

クラスのインスタンスをポインタで扱う場合、delete演算子によりインスタンスが破棄されます。
つまりこのタイミングでデストラクタが呼び出されます。


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

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

//何か処理...

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

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

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