C++入門|コンストラクタとデストラクタ

インスタンス誕生の瞬間に起動し、消える直前に後始末する。コンストラクタとデストラクタは、C++オブジェクトの始まりと終わりを支える自動発動の特別技です。

C++のオブジェクト指向では、クラスをもとにインスタンスを作り、そのインスタンスがメンバ変数とメンバ関数を持って動きます。

ここまで、クラス、インスタンス、メンバ変数、メンバ関数、public、private、カプセル化、セッター、ゲッターなどを学んできました。

しかし、C++のクラスには、通常のメンバ関数とは少し違う、特別なメンバ関数があります。

それが、コンストラクタデストラクタです。

コンストラクタは、インスタンスが生成されたときに自動的に呼び出される特別なメンバ関数です。
デストラクタは、インスタンスが消去される直前に自動的に呼び出される特別なメンバ関数です。

ドラゴンボール風にたとえるなら、コンストラクタは修行カプセルが起動した瞬間に行われる初期設定です。
戦士がカプセルに入った瞬間、戦闘力や蓄積エネルギーを0に整え、修行できる状態にします。

一方、デストラクタは修行カプセルが停止する直前の終了処理です。
修行が終わり、カプセルの電源が落ちる前に、終了メッセージを出したり、後片付けをしたりします。

特別なメンバ関数呼び出されるタイミングドラゴンボール風のイメージ
コンストラクタインスタンス生成時修行カプセルの起動処理
デストラクタインスタンス消去直前修行カプセルの終了処理

通常のメンバ関数は、自分で呼び出す必要があります。

pod.train(1.5);

しかし、コンストラクタとデストラクタは、自分で直接呼び出さなくても、決まったタイミングで自動的に呼び出されます。

この自動実行の仕組みを理解すると、インスタンスの生成と消去の流れがかなり分かりやすくなります。

図:コンストラクタは起動時、デストラクタは終了時に自動発動する

この図が示していること

この図では、インスタンスの生成時にコンストラクタが自動実行され、インスタンスが消える直前にデストラクタが自動実行される流れを表しています。

KiTrainingPod pod; と書くと、KiTrainingPodクラスのインスタンスが生成されます。
その瞬間に、KiTrainingPod()というコンストラクタが呼び出されます。

main関数の処理が終わり、podが不要になる直前には、~KiTrainingPod()というデストラクタが呼び出されます。

コンストラクタとは何か

コンストラクタは、クラスのインスタンスが生成されたときに自動的に呼び出されるメンバ関数です。

たとえば、次のようにインスタンスを作ったとします。

KiTrainingPod pod;

この1行によって、KiTrainingPodクラスのインスタンスpodが生成されます。
そして、そのタイミングでKiTrainingPodクラスのコンストラクタが自動的に呼び出されます。

コンストラクタには、いくつかの重要なルールがあります。

ルール内容
名前クラス名と同じにする
戻り値戻り値の型を書かない
呼び出しインスタンス生成時に自動的に呼び出される
主な役割メンバ変数の初期化などを行う

たとえば、クラス名がKiTrainingPodなら、コンストラクタの名前もKiTrainingPodになります。

KiTrainingPod();

戻り値の型は書きません。
voidも書きません。

void KiTrainingPod();

このようには書きません。

コンストラクタは、インスタンスを使い始める前の準備処理を書く場所です。

ドラゴンボール風に言えば、修行カプセルが起動した瞬間に、戦闘力メーターや蓄積エネルギーを初期状態に整える処理です。

デストラクタとは何か

デストラクタは、インスタンスが消去される直前に自動的に呼び出されるメンバ関数です。

たとえば、main関数の中で作ったインスタンスは、main関数が終わるときに消去されます。

その直前に、デストラクタが呼び出されます。

デストラクタにも、いくつかのルールがあります。

ルール内容
名前クラス名の前に~を付ける
戻り値戻り値の型を書かない
呼び出しインスタンス消去直前に自動的に呼び出される
主な役割終了処理や後片付けを行う

クラス名がKiTrainingPodなら、デストラクタの名前は~KiTrainingPodになります。

~KiTrainingPod();

デストラクタも、戻り値の型は書きません。

void ~KiTrainingPod();

このようには書きません。

ドラゴンボール風に言えば、デストラクタは修行カプセルが停止する直前に行う終了処理です。
修行ログを閉じたり、終了メッセージを表示したり、後片付けを行うイメージです。

今回作成するサンプルプログラム

今回は、KiTrainingPodクラスを作ります。

KiTrainingPodは、修行カプセルを表すクラスです。

このクラスでは、現在の気の出力を表すm_powerと、修行によって蓄積された総エネルギーを表すm_totalEnergyを管理します。

メンバ変数内容
m_power現在の気の出力
m_totalEnergy総蓄積エネルギー

また、次のメンバ関数を用意します。

メンバ関数内容
KiTrainingPodコンストラクタ
~KiTrainingPodデストラクタ
setPower気の出力を設定する
getPower気の出力を取得する
getTotalEnergy総蓄積エネルギーを取得する
train指定した時間だけ修行する

ファイル構成は、次の3つです。

ファイル役割
ki_training_pod.hKiTrainingPodクラスの宣言
ki_training_pod.cppコンストラクタ、デストラクタ、メンバ関数の定義
main.cppインスタンスを生成して修行処理を実行する

プロジェクト名はChap4_01のままにします。
ファイル名は、修行カプセルの内容に合わせて、ki_training_pod.hとki_training_pod.cppにします。

KiTrainingPodクラスの宣言

まず、KiTrainingPodクラスの宣言を書きます。

プロジェクト/ファイル名: Chap4_01/ki_training_pod.h

#ifndef _KI_TRAINING_POD_H_
#define _KI_TRAINING_POD_H_

class KiTrainingPod {
public:
    // コンストラクタ
    KiTrainingPod();

    // デストラクタ
    ~KiTrainingPod();

    // 気の出力を設定する
    void setPower(double power);

    // 気の出力を取得する
    double getPower();

    // 総蓄積エネルギーを取得する
    double getTotalEnergy();

    // 指定した時間だけ修行する
    void train(double hour);

private:
    // 現在の気の出力
    double m_power;

    // 総蓄積エネルギー
    double m_totalEnergy;
};

#endif // _KI_TRAINING_POD_H_

このヘッダーファイルでは、KiTrainingPodクラスを宣言しています。

注目したいのは、次の2つです。

KiTrainingPod();
~KiTrainingPod();

KiTrainingPod()がコンストラクタです。
~KiTrainingPod()がデストラクタです。

どちらも戻り値の型を書いていません。

通常のメンバ関数であれば、voidやdoubleなどの戻り値の型を書きます。

void setPower(double power);
double getPower();

しかし、コンストラクタとデストラクタは特別なメンバ関数なので、戻り値の型を書きません。

privateなメンバ変数

KiTrainingPodクラスでは、m_powerとm_totalEnergyをprivateにしています。

private:
    double m_power;
    double m_totalEnergy;

m_powerは現在の気の出力です。
m_totalEnergyは修行によって蓄積された総エネルギーです。

これらは、クラスの外から直接変更させないようにprivateにしています。

main.cppから直接次のようには書きません。

pod.m_power = 400;

正しくは、publicなsetPowerを通して値を設定します。

pod.setPower(400);

これはカプセル化の考え方です。

修行カプセル内部の大切なデータはprivateで守り、外からは決められた操作だけを使います。

コンストラクタとデストラクタを定義する

次に、ki_training_pod.cppにメンバ関数の中身を書きます。

プロジェクト/ファイル名: Chap4_01/ki_training_pod.cpp

#include "ki_training_pod.h"
#include <iostream>

using namespace std;

// コンストラクタ
KiTrainingPod::KiTrainingPod() : m_power(0.0), m_totalEnergy(0.0) {
    cout << "KiTrainingPodインスタンス起動" << endl;
}

// デストラクタ
KiTrainingPod::~KiTrainingPod() {
    cout << "KiTrainingPodインスタンス終了" << endl;
}

// 気の出力を設定する
void KiTrainingPod::setPower(double power) {
    m_power = power;
}

// 気の出力を取得する
double KiTrainingPod::getPower() {
    return m_power;
}

// 総蓄積エネルギーを取得する
double KiTrainingPod::getTotalEnergy() {
    return m_totalEnergy;
}

// 指定した時間だけ修行する
void KiTrainingPod::train(double hour) {
    cout << "気の出力" << m_power << "で" << hour << "時間修行" << endl;
    cout << m_power * hour << "ポイントの気を蓄積しました。" << endl;

    // 修行で蓄積した気を総蓄積エネルギーに加算する
    m_totalEnergy += m_power * hour;
}

コンストラクタの定義

コンストラクタの定義は、次の部分です。

KiTrainingPod::KiTrainingPod() : m_power(0.0), m_totalEnergy(0.0) {
    cout << "KiTrainingPodインスタンス起動" << endl;
}

ここでは、KiTrainingPodクラスのコンストラクタを定義しています。

KiTrainingPod::KiTrainingPod()

クラスの外側でメンバ関数を定義するため、KiTrainingPod::を付けています。

そして、関数名もKiTrainingPodです。
クラス名と同じ名前になっているので、これはコンストラクタです。

メンバ初期化リスト

コンストラクタの次の部分に注目しましょう。

: m_power(0.0), m_totalEnergy(0.0)

これは、メンバ初期化リストです。

メンバ初期化リストを使うと、コンストラクタの本体が実行される前に、メンバ変数を初期化できます。

メンバ変数初期値
m_power0.0
m_totalEnergy0.0

つまり、KiTrainingPodインスタンスが生成された時点で、m_powerとm_totalEnergyは0.0になります。

ドラゴンボール風に言えば、修行カプセルが起動した瞬間に、気の出力メーターと総蓄積エネルギーが0にリセットされるイメージです。

図:コンストラクタのメンバ初期化リスト

この図が示していること

この図では、コンストラクタのメンバ初期化リストによって、インスタンス生成直後にm_powerとm_totalEnergyが0.0になることを表しています。

コンストラクタは、インスタンス生成時に自動的に呼び出されます。
そのため、KiTrainingPod pod; と書いた時点で、修行カプセル内部の値が安全な初期状態になります。

初期化リストの書き方

コンストラクタでメンバ変数を初期化するときは、次のような形で書きます。

クラス名::クラス名() : メンバ変数1(初期値1), メンバ変数2(初期値2) {
    // コンストラクタ本体の処理
}

今回のサンプルでは、次のようになっています。

KiTrainingPod::KiTrainingPod() : m_power(0.0), m_totalEnergy(0.0) {
    cout << "KiTrainingPodインスタンス起動" << endl;
}

複数のメンバ変数を初期化する場合は、コンマで区切ります。

m_power(0.0), m_totalEnergy(0.0)

メンバ変数の並びは、ヘッダーファイルで宣言した順番に合わせて書くと読みやすくなります。

今回のヘッダーファイルでは、privateメンバ変数は次の順番です。

double m_power;
double m_totalEnergy;

そのため、初期化リストでも同じ順番にしています。

m_power(0.0), m_totalEnergy(0.0)

このように並びをそろえると、初期化漏れや読み間違いに気づきやすくなります。

コンストラクタ本体の処理

初期化リストのあとにある波かっこの中は、コンストラクタ本体です。

{
    cout << "KiTrainingPodインスタンス起動" << endl;
}

ここには、通常のメンバ関数と同じように処理を書けます。

今回のサンプルでは、インスタンスが生成されたことを確認しやすいように、起動メッセージを表示しています。

実行すると、KiTrainingPod pod; のタイミングで次の文字が表示されます。

KiTrainingPodインスタンス起動

このように、コンストラクタ本体の処理も、インスタンス生成時に自動的に実行されます。

デストラクタの定義

デストラクタの定義は、次の部分です。

KiTrainingPod::~KiTrainingPod() {
    cout << "KiTrainingPodインスタンス終了" << endl;
}

デストラクタは、クラス名の前に~を付けた名前にします。

~KiTrainingPod()

コンストラクタと同じく、デストラクタも戻り値の型を書きません。

デストラクタは、インスタンスが消去される直前に自動的に呼び出されます。

今回のプログラムでは、main関数の中でpodを作っています。
そのため、main関数が終わるときにpodが消去され、その直前にデストラクタが呼ばれます。

実行結果の最後に、次の文字が表示されます。

KiTrainingPodインスタンス終了

これは、デストラクタが自動的に呼び出されたことを表しています。

train関数で総蓄積エネルギーを増やす

train関数では、指定した時間だけ修行した結果を表示し、総蓄積エネルギーを増やします。

void KiTrainingPod::train(double hour) {
    cout << "気の出力" << m_power << "で" << hour << "時間修行" << endl;
    cout << m_power * hour << "ポイントの気を蓄積しました。" << endl;

    // 修行で蓄積した気を総蓄積エネルギーに加算する
    m_totalEnergy += m_power * hour;
}

m_powerは現在の気の出力です。
hourは修行時間です。

m_power * hourによって、その修行で蓄積した気のポイントを求めています。

内容
m_power現在の気の出力
hour修行時間
m_power * hourその修行で蓄積する気
m_totalEnergyこれまでの総蓄積エネルギー

m_totalEnergyには、修行するたびに値が加算されます。

m_totalEnergy += m_power * hour;

このようにすることで、複数回の修行結果を合計できます。

main.cppでインスタンスを生成して使う

最後に、main.cppを見ていきます。

プロジェクト/ファイル名: Chap4_01/main.cpp

#include <iostream>
#include "ki_training_pod.h"

using namespace std;

int main(int argc, char** argv) {
    // インスタンスを生成する
    KiTrainingPod pod;

    // 気の出力を設定する
    pod.setPower(400);

    // 指定した時間だけ修行する
    pod.train(1.5);

    // 気の出力を再設定する
    pod.setPower(650);

    // 指定した時間だけ修行する
    pod.train(2.0);

    // 総蓄積エネルギーを表示する
    cout << "総蓄積エネルギー:" << pod.getTotalEnergy() << "ポイント" << endl;

    // プログラムが正常に終わったことを示す
    return 0;
}

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

KiTrainingPodインスタンス起動
気の出力400で1.5時間修行
600ポイントの気を蓄積しました。
気の出力650で2時間修行
1300ポイントの気を蓄積しました。
総蓄積エネルギー:1900ポイント
KiTrainingPodインスタンス終了

この実行結果を見ると、コンストラクタとデストラクタが自動的に呼び出されていることが分かります。

最初に表示される次の行は、コンストラクタによるものです。

KiTrainingPodインスタンス起動

最後に表示される次の行は、デストラクタによるものです。

KiTrainingPodインスタンス終了

main.cppでは、コンストラクタやデストラクタを直接呼び出していません。
それでも自動的に実行されています。

処理の流れを順番に確認する

今回のプログラムの流れを整理すると、次のようになります。

手順処理内容
1KiTrainingPod pod;インスタンスを生成し、コンストラクタが呼ばれる
2pod.setPower(400);気の出力を400に設定する
3pod.train(1.5);400 × 1.5 = 600ポイントを蓄積する
4pod.setPower(650);気の出力を650に設定する
5pod.train(2.0);650 × 2.0 = 1300ポイントを蓄積する
6pod.getTotalEnergy();600 + 1300 = 1900ポイントを取得する
7main関数終了デストラクタが呼ばれる

最初のKiTrainingPod pod;で、コンストラクタが呼び出されます。
このとき、m_powerとm_totalEnergyは0.0に初期化されます。

その後、setPowerとtrainを使って、修行を2回行います。

1回目の修行では、400 × 1.5 = 600ポイントです。
2回目の修行では、650 × 2.0 = 1300ポイントです。

合計は1900ポイントになります。

最後にmain関数が終わると、podが消去される直前にデストラクタが呼び出されます。

図:main関数内での生成、修行、消去の流れ

この図が示していること

この図では、main関数の中でKiTrainingPodインスタンスが生成され、修行処理を行い、最後に消去される流れを表しています。

KiTrainingPod pod;のタイミングでコンストラクタが呼ばれます。
その後、setPowerとtrainによって修行処理が行われます。
main関数が終了すると、podが消える直前にデストラクタが呼ばれます。

コンストラクタは一度だけ呼び出される

コンストラクタは、インスタンスが生成されるときに一度だけ呼び出されます。

今回のmain.cppでは、次の行でpodを生成しています。

KiTrainingPod pod;

この時点で、コンストラクタが一度だけ呼ばれます。

その後、setPowerやtrainを何回呼び出しても、コンストラクタは再び呼び出されません。

pod.setPower(400);
pod.train(1.5);
pod.setPower(650);
pod.train(2.0);

これらは通常のメンバ関数なので、自分で呼び出したときだけ実行されます。

コンストラクタは、インスタンスの生成時に自動的に一度だけ実行される初期化処理です。

デストラクタも一度だけ呼び出される

デストラクタは、インスタンスが消去される直前に一度だけ呼び出されます。

今回のpodはmain関数の中で作られています。

int main(int argc, char** argv) {
    KiTrainingPod pod;

    // 処理

    return 0;
}

main関数が終わると、podは不要になります。
その直前に、デストラクタが呼び出されます。

デストラクタは、自分で明示的に呼び出すものではなく、インスタンスの寿命が終わるタイミングで自動的に呼び出されます。

通常のメンバ関数との違い

コンストラクタとデストラクタは、通常のメンバ関数とは違います。

種類名前の決まり戻り値呼び出し方
通常のメンバ関数自由に決められるvoidやdoubleなどを書く自分で呼び出す
コンストラクタクラス名と同じ書かない生成時に自動呼び出し
デストラクタ~クラス名書かない消去直前に自動呼び出し

たとえば、setPowerは通常のメンバ関数です。

void setPower(double power);

呼び出すときは、自分で次のように書きます。

pod.setPower(400);

一方、コンストラクタは自分でpod.KiTrainingPod()のように呼び出すものではありません。

KiTrainingPod pod;

この生成処理によって、自動的に呼び出されます。

デストラクタも同じで、自分でpod.~KiTrainingPod()のように呼び出すのではなく、インスタンスが消える直前に自動的に呼び出されます。

コンストラクタで初期化する理由

コンストラクタで初期化を行う理由は、インスタンスを安全な状態で使い始めるためです。

もしm_totalEnergyが初期化されていなければ、最初からどんな値が入っているか分かりません。
その状態でtrainを実行すると、正しい合計値にならない可能性があります。

そこで、コンストラクタで次のように初期化します。

KiTrainingPod::KiTrainingPod() : m_power(0.0), m_totalEnergy(0.0) {
    cout << "KiTrainingPodインスタンス起動" << endl;
}

これにより、インスタンス生成直後から、m_powerもm_totalEnergyも0.0になります。

ドラゴンボール風に言えば、修行を始める前に、カプセルの気圧、重力、気のメーターを安全な初期状態に整えるようなものです。

最初の状態をきちんと決めておくことで、そのあとの処理が安定します。

デストラクタで終了処理を書く理由

デストラクタは、インスタンスが消える直前の処理を書く場所です。

今回のサンプルでは、終了したことを分かりやすくするために、メッセージを表示しています。

KiTrainingPod::~KiTrainingPod() {
    cout << "KiTrainingPodインスタンス終了" << endl;
}

現時点では、文字を表示するだけですが、C++では今後、new演算子で確保したメモリをdelete演算子で解放するような処理にもつながっていきます。

つまり、デストラクタは、インスタンスが使い終わる直前に、後片付けをするための大切な場所です。

ドラゴンボール風に言えば、修行カプセルの停止時に、重力制御を戻し、ログを閉じ、エネルギー残量を整理するようなイメージです。

メモリとインスタンスの寿命につながる考え方

コンストラクタとデストラクタは、インスタンスの寿命と深く関係しています。

インスタンスが作られると、メモリ上にそのインスタンスの領域が用意されます。
そして、その領域が使えるようになるタイミングで、コンストラクタが呼び出されます。

インスタンスが不要になると、その領域は解放されます。
その直前に、デストラクタが呼び出されます。

タイミング起こること
インスタンス生成メモリ上に領域が用意される
生成直後コンストラクタが呼び出される
利用中メンバ関数を呼び出して処理する
インスタンス消去直前デストラクタが呼び出される
消去後インスタンスは使えなくなる

今後、new演算子やdelete演算子を学ぶと、インスタンスの生成と消去をより明示的に扱う場面が出てきます。

そのときにも、コンストラクタとデストラクタの考え方はとても重要になります。

コンストラクタとデストラクタで押さえたいポイント

コンストラクタとデストラクタでは、次のポイントをしっかり押さえておきましょう。

ポイント内容
コンストラクタインスタンス生成時に自動的に呼び出される
デストラクタインスタンス消去直前に自動的に呼び出される
コンストラクタ名クラス名と同じ
デストラクタ名クラス名の前に~を付ける
戻り値どちらも戻り値の型を書かない
主な役割コンストラクタは初期化、デストラクタは終了処理
初期化リストコンストラクタでメンバ変数を初期化できる

C++のクラスでは、インスタンスを作った瞬間から安全に使えるようにすることが大切です。

そのために、コンストラクタでメンバ変数を初期化します。

そして、インスタンスが消える直前には、デストラクタで終了処理を行えます。

修行カプセルで考えると、コンストラクタは起動時の初期設定、デストラクタは停止時の後片付けです。

この2つを理解すると、C++のオブジェクトがどのように生まれ、どのように消えていくのかが見えてきます。