Skip to content

デストラクタ

オブジェクトを破棄する際に呼び出されるメンバ関数をデストラクタといいます。 デストラクタはリソースの解放を行うために使用します。

RAII (Resource Acquisition Is Initialization)

C++ では ガベージコレクション の代わりにデストラクタによってリソース解放を行います。 デストラクタによるリソース解放のタイミングは明示的であるため、 C++ ではデフォルトで RAII が適用されます。

デストラクタを持たずに RAII を実現できる言語もありますが、 一般に何らかの明示的な処理を必要とします。

たとえば Python では明示的に with 文を使用する必要があります。

~ にクラス名をつけた名前で戻り値がない関数がデストラクタになります。 デストラクタは引数をもちません。

#include <iostream>

class DynamicArray {
 public:
    DynamicArray(int size, int initial_value) {
        data_ = new int[size];
        for (auto i = 0; i < size; ++i) {
            data_[i] = initial_value;
        }
    }

    ~DynamicArray() {
        std::cout << "DynamicArray::~DynamicArray() is called." << std::endl;
        delete[] data_;
    }

    void Set(int index, int value) {
        data_[index] = value;
    }

    int Get(int index) const {
        return data_[index];
    }

 private:
    int* data_;
};

int main() {
    DynamicArray d(5, 1);
    std::cout << d.Get(2) << std::endl;
    d.Set(2, 10);
    std::cout << d.Get(2) << std::endl;

    return 0;
}

明示的に定義しない場合、 コンパイラが暗黙的にデストラクタを定義します。

クラス宣言とは別に定義

クラス宣言とは別にデストラクタを定義するには次のようにします。

class DynamicArray {
 public:
    DynamicArray(int size, int initial_value) {
        data_ = new int[size];
        for (auto i = 0; i < size; ++i) {
            data_[i] = initial_value;
        }
    }

    ~DynamicArray();

    void Set(int index, int value) {
        data_[index] = value;
    }

    int Get(int index) const {
        return data_[index];
    }

 private:
    int* data_;
};

DynamicArray::~DynamicArray() {
    std::cout << "DynamicArray::~DynamicArray() is called." << std::endl;
    delete[] data_;
}

継承

派生クラスのデストラクタは基底クラスのデストラクタを暗黙的に呼び出します。

destructor_inheritance.cc

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>

class BasicArray {
 public:
    ~BasicArray() {
        std::cout << "BasicArray::~BasicArray() is called." << std::endl;
    }

    virtual void Set(int index, int value) = 0;
    virtual int Get(int index) const = 0;
};

class DynamicArray : public BasicArray {
 public:
    DynamicArray(int size, int initial_value) {
        data_ = new int[size];
        for (auto i = 0; i < size; ++i) {
            data_[i] = initial_value;
        }
    }

    ~DynamicArray() {
        std::cout << "DynamicArray::~DynamicArray() is called." << std::endl;
        delete[] data_;
    }

    void Set(int index, int value) { data_[index] = value; }

    int Get(int index) const { return data_[index]; }

 private:
    int* data_;
};

int main() {
    DynamicArray d(5, 1);
    std::cout << d.Get(2) << std::endl;
    d.Set(2, 10);
    std::cout << d.Get(2) << std::endl;

    return 0;
}

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

1
10
DynamicArray::~DynamicArray() is called.
BasicArray::~BasicArray() is called.

デストラクタの実行順序は、必ず次の順序になります。

  1. 派生クラスのデストラクタ
  2. 基底クラスのデストラクタ

仮想デストラクタ

アップキャストして基底クラスのポインタで扱う場合、 基底クラスのデストラクタだけが呼び出されて 派生クラスのデストラクタは呼び出されなくなります。

destructor_non_virtual.cc

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <memory>

class BasicArray {
 public:
    ~BasicArray() {
        std::cout << "BasicArray::~BasicArray() is called." << std::endl;
    }

    virtual void Set(int index, int value) = 0;
    virtual int Get(int index) const = 0;
};

class DynamicArray : public BasicArray {
 public:
    DynamicArray(int size, int initial_value) {
        data_ = new int[size];
        for (auto i = 0; i < size; ++i) {
            data_[i] = initial_value;
        }
    }

    ~DynamicArray() {
        std::cout << "DynamicArray::~DynamicArray() is called." << std::endl;
        delete[] data_;
    }

    void Set(int index, int value) { data_[index] = value; }

    int Get(int index) const { return data_[index]; }

 private:
    int* data_;
};

int main() {
    std::unique_ptr<BasicArray> b(new DynamicArray(5, 1));
    std::cout << b->Get(2) << std::endl;
    b->Set(2, 10);
    std::cout << b->Get(2) << std::endl;

    return 0;
}

この例では 派生クラス DynamicArray をアップキャストして 基底クラス BasicArray のスマートポインタで扱っています。

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

1
10
BasicArray::~BasicArray() is called.

DynamicArray のデストラクタが呼ばれておらずメモリリークが発生してしまいます。

このような問題を防ぐために、 基底クラスのデストラクタは仮想関数にします。 派生クラスではデストラクタをオーバーロードすることになるため override をつけます。

destructor_virtual.cc

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <memory>

class BasicArray {
 public:
    virtual ~BasicArray() {
        std::cout << "BasicArray::~BasicArray() is called." << std::endl;
    }

    virtual void Set(int index, int value) = 0;
    virtual int Get(int index) const = 0;
};

class DynamicArray : public BasicArray {
 public:
    DynamicArray(int size, int initial_value) {
        data_ = new int[size];
        for (auto i = 0; i < size; ++i) {
            data_[i] = initial_value;
        }
    }

    ~DynamicArray() override {
        std::cout << "DynamicArray::~DynamicArray() is called." << std::endl;
        delete[] data_;
    }

    void Set(int index, int value) { data_[index] = value; }

    int Get(int index) const { return data_[index]; }

 private:
    int* data_;
};

int main() {
    std::unique_ptr<BasicArray> b(new DynamicArray(5, 1));
    std::cout << b->Get(2) << std::endl;
    b->Set(2, 10);
    std::cout << b->Get(2) << std::endl;

    return 0;
}

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

1
10
DynamicArray::~DynamicArray() is called.
BasicArray::~BasicArray() is called.

STLコンテナクラスの継承

STLコンテナクラスのデストラクタは仮想関数になっていません。 そのためSTLコンテナを継承したクラスを STLコンテナにアップキャストして使用してはいけません。