コンストラクタ/デストラクタ


クラスには、初期化を行うための特別な関数と、 終了処理を行うための特別な関数を定義できます。

コンストラクタ

クラスには、初期化を行うための特別な関数を定義することができます。 例として Color クラスを考えてみましょう。

class Color
{
public:
    UInt08 r;
    UInt08 g;
    UInt08 b;
    
    Color()
    {
        r = g = b = 0;
    }

    Color(UInt08 n)
    {
        r = g = b = n;
    }

    Color(UInt08 r0, UInt08 g0, UInt08 b0)
    {
        r = r0;
        g = g0;
        b = b0;
    }
};

クラス名と同じ名前で戻り値の型の指定のない Color という関数が定義されています。 これがコンストラクタと呼ばれるもので、 クラスのオブジェクトを作成するときの初期化を行うための関数です。

Color color1;              // Color() で初期化します
Color color2(64);          // Color(UInt08) で初期化します
Color color3(64,192,192);  // Color(UInt08,UInt08,UInt08) で初期化します

コンストラクタは必ずしも定義する必要はありません。 その場合は初期化は何ひとつ行われません。

コンストラクタは、 new 演算子を使ってヒープ上に オブジェクトを作成するときに呼び出すこともできます。

Color * pcolor1 = new Color;              // Color() で初期化します
Color * pcolor2 = new Color(64);          // Color(UInt08) で初期化します
Color * pcolor3 = new Color(64,192,192);  // Color(UInt08,UInt08,UInt08) で初期化します
【Java では】 Java にもコンストラクタがあり、C++ と同じく、 クラスと同名の戻り値のない関数で定義します。

デストラクタ

クラスには、終了処理を行うための特別な関数を定義することができます。 これをデストラクタと呼び、 「"~"+クラス名」という関数名で定義します。 デストラクタには (通常は) 引数を指定しません。

デストラクタは、スタック上に作成されたオブジェクトが自動的に破棄されるときや、 ヒープ上に作成されたオブジェクトを delete 演算子により明示的に破棄するときに、 必ず呼び出されます。 そのため、デストラクタは終了処理を記述するのに最適な関数です。

例として、指定されたサイズのメモリを割り当てて管理するための Buffer というクラスを考えてみましょう。

class Buffer
{
private:
    UIntN   size;      // バッファ サイズ
    VoidPtr pointer;   // バッファ ポインタ

public:
    Buffer()
    {
         size = 0;
         pointer = NULL;
    }
    Buffer(UIntN sz)
    {
        size = sz;
        pointer = MALLOC(sz);
    }
    VoidPtr GetPointer()
    {
        return pointer;
    }
    ~Buffer()
    {
        if (pointer != NULL)
            FREE(pointer);
    }
};

このクラスは、コンストラクタで指定したサイズのメモリを割り当てます。 割り当てたメモリ ポインタは GetPointer() により取得することができます。

Buffer クラスは、そのデストラクタ ~Buffer() でメモリを解放しています。 デストラクタは、オブジェクトが破棄されるときに必ず呼び出されるため、 このような実装にしておけばメモリリークは決して発生しません。

次のコードでは、MyFunction() 関数の中でスタックに作成された Buffer オブジェクトは、関数を抜けるときに破棄され、 デストラクタが呼び出されます。

Void MyFunction()
{
    Buffer buf(256);                // 256 バイトのバッファを確保
    VoidPtr p = buf.GetPointer();   // バッファ ポインタを取得

    ... p を利用するコード ...

}   // 関数が終了するときに Buffer オブジェクトが破棄され、
    // Buffer のデストラクタが呼び出される。

次のコードでは、ヒープに作成された Buffer オブジェクトは、 delete 演算子により破棄され、デストラクタが呼び出されます。

    Buffer * pbuf = new Buffer(256);    // 256 バイトのバッファを確保
    VoidPtr p = pbuf->GetPointer();     // バッファ ポインタを取得

    ... p を利用するコード ...

    delete pbuf;    // delete 演算子により Buffer オブジェクトが破棄され、
                    // Buffer のデストラクタが呼び出される。
【Java では】 Java では、デストラクタではなくファイナライザと呼ばれます。 関数名は finalize() という決まった名前になります。 Java のファイナライザはいつ呼び出されるか予測することができないという欠点があります。

この章のまとめ

やってみよう

次のようなコードを実行してみましょう!
どのような結果になるでしょうか?
この結果からどのようなことが分かりますか?

class Plant
{
public:
    Plant()
    {
        DBGPRINTF("Plant::Plant()");
    }

    ~Plant()
    {
        DBGPRINTF("~Plant::Plant()");
    }
};

class Weed : public Plant
{
public:
    Weed()
    {
        DBGPRINTF("Weed::Weed()");
    }

    ~Weed()
    {
        DBGPRINTF("~Weed::Weed()");
    }
};

Void TestCtorDtor(Void)
{
    Weed w;
}