C++によるプログラミング入門18
デストラクタ

 こんにちは。みなさん、いかがお過ごしですか?あまり計画性もなく、毎週考えながら書いた「入門」ですが、ちゃんと読んでくれる人がいてうれしいです。今回はデストラクタという関数の話です。
 クラスにはコンストラクタという特別な関数があるということはずいぶん前に説明しました。これはクラスのインスタンス(オブジェクト)を生成する際にそれを初期化(つまり、はじめのデータの値などを代入したりする)したりするのに使う関数でした。クラスには実はもう一つ特別な関数「デストラクタ」というものがあるのです。この関数はインスタンスが破棄されるときに呼び出される関数で、後始末のために呼び出されるものです。デストラクタはとても重要な関数ですが、その重要さを初心者に説明するのは少し難しいのです。「重要さ」は、もう少しあとで説明することにして、今回は「デストラクタとはどういう関数か」を見てみたいと思います。
 まず、次のようなクラスを考えてみましょう。

class Nanika
{
    int datum;
public:
    Nanika(int x) : datum(x){
        cout << "Nanikaのインスタンス" << datum << "が生成されました。" << endl;
    }
    void func() const{
        cout << "Nanikaのインスタンス" << datum << "のfuncが呼ばれました。" <<endl; 
    }
};

 これはNanika(何か)という練習用のクラスです。もう、わかると思いますが、datumがプライベートなデータで、コンストラクタはユーザまたはプログラマから整数値をもらってインスタンス(オブジェクト)を初期化しています。いつもはこれだけなのですが、ここでは、さらに、インスタンスが生成されたことを画面に書くようにしてあります。また、func()は自分が呼ばれたら(つまり使われたら)、「呼ばれました」とだけ書き出す関数です。こんなクラス何の役にたつんだと思った人は、お目が高い!!!別に実用的な意味はありません。しかし、C++プログラムがどのように実行されるか自分で見てみるにはとても良い例だと思います。
このクラスにデストラクタを付け加えます。それは、

    ~Nanika(){.....}

という形の関数です。ここでクラス名Nanikaの前にはチルダ(にょろ、と呼ぶ人も多いですね)があります。デストラクタは、必ずこの形でないといけません。{.....}の中の点々のところには実際の処理を書きます。この「処理」とは、つまり「インスタンスの後始末」ということです。しかし、今のクラスでは特別な後始末が不要なので、わざわざデストラクタを書くことはありません。(まあ、今までの入門の例はみなそうですね。)後始末が必要になる例はまた後で、ということで、今日はとにかくデストラクタを書いて遊んでみましょう。今の例では、次のようにします。

    ~Nanika(){
        cout << "Nanikaのインスタンス" << datum << "が消滅しました。" << endl;
    }

これでインスタンスが破棄されたとき(消滅したとき)、それが画面に出てわかるわけです。ちょっと、プログラムを作ってみましょう。

//dest_sample.cpp
#include <iostream>
using namespace std;

class Nanika
{
    int datum;
public:
    //コンストラクタ
    Nanika(int x) : datum(x){
        cout << "Nanikaのインスタンス" << datum << "が生成されました。" << endl;
    }
    void func() const{
        cout << "Nanikaのインスタンス" << datum << "のfuncが呼ばれました。" <<endl; 
    }
    //デストラクタ
    ~Nanika(){
        cout << "Nanikaのインスタンス" << datum << "が消滅しました。" << endl;
    }
};

int main()
{
    //プログラマがはじめから数値1、2を与えてインスタンスOne、Twoをつくる
    Nanika One(1), Two(2);

    One.func();
    Two.func();
}

 ここで、ちょっと一言。私はいつも、コンストラクタの引数を説明するときに「ユーザまたはプログラマから値をもらって、」と書きながら、「私の例はいつもユーザからもらうものばかりだなぁ」と思っていました。ここでようやくプログラマがあらかじめ初期値を与える例を出すことができました。(ほっ。)こんな風にしてよいのです。(もう、わかっていましたよね?)余談ですが、Nekoの生成なら

    Neko Dora("ボス");

なんてしても良いわけです。
 さて、本題にもどって、とりあえず上のプログラムを実行してみてください。

 こういう順序でインスタンスが生成され、使われ、破棄されるんだなぁと思ってください。(デストラクタはコンストラクタの逆をたどるように決められているのです。)
 こういう話はあまりおもしろくないという人も多いでしょうが、ステップアップするためには、徐々に考えていかなければならない問題でもあります。つまり、プログラムを見ながら、「このオブジェクトはここで破棄されるのかな、それともここまで残っているのかな」なんてことを考えるようにすぐなるということなのです。こういう疑問に対する答えは(よく読めば)教科書に出ていますが、上のようなコンストラクタ、デストラクタを作って自分で確かめるのが一番だと思います。

 最近は継承の勉強をしているのでした。継承されたクラスのインスタンスはどのような一生を送るのでしょう。それを知るための例を自分で考えてみてください。

一例:

//dest_sample2.cpp
#include <iostream>
using namespace std;

class Nanika
{
    int datum;
public:
    //コンストラクタ
    Nanika(int x) : datum(x){
        cout << "Nanikaのインスタンス" << datum << "が生成されました。" << endl;
    }
    void func() const{
        cout << "Nanikaのインスタンス" << datum << "のfuncが呼ばれました。" <<endl; 
    }
    //datumの値を戻す関数をつけておく
    int get_datum() const { return datum; }
    //デストラクタ
    ~Nanika(){
        cout << "Nanikaのインスタンス" << datum << "が消滅しました。" << endl;
    }
};

//Nanikaの派生クラス
class NanikaNoKo : public Nanika
{
public:
    //コンストラクタ
    NanikaNoKo(int x) : Nanika(x){
        cout << "NanikaNoKoのインスタンス" << get_datum() << "が生成されました。" << endl;
    }
    //デストラクタ
    ~NanikaNoKo(){
        cout << "NanikaNoKoのインスタンス" << get_datum() << "が消滅しました。" << endl;
    }
    void func() const{
        cout << "NanikaNoKoのインスタンス" << get_datum() << "funcが呼ばれました。" << endl;
    }
};

//NanikaNoKoの派生クラス
class NanikaNoMago : public NanikaNoKo
{
public:
    //コンストラクタ
    NanikaNoMago(int x):NanikaNoKo(x){
        cout << "NanikaNoMagoのインスタンス" <<get_datum() << "が生成されました。" << endl;
    }
    ~NanikaNoMago(){
        cout << "NanikaNoMagoのインスタンス" << get_datum() << "が消滅しました。" << endl;
    }
    void func() const{
        cout << "NanikaNoMagoのインスタンス" << get_datum() << "funcが呼ばれました。" << endl;
    }
};

int main()
{
    Nanika One(1), Two(2);
    NanikaNoKo Three(3);
    NanikaNoMago Four(4);

    One.func();
    Two.func();
    Three.func();
    Four.func();
}

Nanikaのdatumを派生クラスでも使いたいので、Nanikaにはget_datumというdatumを戻す関数を付け加えました。このプログラムを実行すると次のようになります。

例えば、オブジェクトThreeの生成のときに、

Nanikaのインスタンス3が生成されました。
NanikaNoKoのインスタンス3が生成されました。

と出るのはなぜだかわかるでしょうか?カンのいい人はわかったかもしれません。NanikaNoKoはNanikaの派生クラスなのですが、これは、簡単にいうと、Nanikaがまずあって、そのまわりに「差分」がくっついてNanikaNoKoになっているわけです。

 そのため、まず、基底クラスNanikaのコンストラクタが呼び出され、次に派生クラスNanikaNoKoのコンストラクタが呼び出されるのです。
 デストラクタが呼び出される順番はその逆です。実行例を見ながら、少し考えてみてください。


 今回はこの辺にしましょうね。^^;)少し、「まじめな話題」だったのですが、つまらなかった人はしばらくこの回のことは無視していてよいと思います。ただ、そのうち必ず必要な知識となるので、あんなことが書いてあったけな、と覚えておいてください。
今回の話をおもしろいと思った人は自分で例をつくって遊んでみてください。いろいろな知識が増えていくと思います。

目次のページ
前のページ  後のページ