【C++】コンストラクタとデストラクタとは?|オブジェクトの初期化と後始末を理解する

サムネイル画像 C++

今回は、C++のクラスでかなり重要な「コンストラクタとデストラクタ」について解説していきます。

Player player; と書いた瞬間に何が起きているの?」
「コンストラクタって何のためにあるの?」
「デストラクタはいつ呼ばれるの?」

こんな疑問はありませんか?

クラスを学び始めると、早い段階で出てくるのがこの2つです。最初は少し難しく見えますが、考え方はそこまで複雑ではありません。コンストラクタは「作られる時の初期化」デストラクタは「消える時の後始末」です。

この記事を読み終えると、あなたはコンストラクタとデストラクタの意味・呼ばれるタイミング・使い方・初心者がハマりやすいミスまで理解できるようになると思いますので、ぜひ最後まで読んでいただけると嬉しいです。

コンストラクタとデストラクタとは?

まず結論からいうと、コンストラクタはオブジェクトが生成される時に自動で呼ばれる関数です。デストラクタはオブジェクトが破棄される時に自動で呼ばれる関数です。

  • コンストラクタ:初期化に使う
  • デストラクタ:後始末に使う
  • どちらも自分で直接呼ぶことは基本的にない
  • オブジェクトの寿命とセットで理解するのが大事

クラスの基本自体がまだ不安な方は、クラスの記事もあわせて読むと、かなり理解しやすいです。

なぜコンストラクタが必要?

たとえばPlayerクラスを作った時、HPや座標などの初期値を毎回手で代入していると、設定漏れが起きやすいです。

そこでコンストラクタを使えば、Player player; と書いただけで、生成と同時にHPや名前などを自動で初期化できます。つまり、「作られた直後から正しい状態にしておく」ための仕組みですね。

Player player; と書いた瞬間に何が起きている?

ここはかなり大事です。Player player; と書くと、まず player というオブジェクトのための領域が用意され、その後でコンストラクタが自動的に呼ばれます。

逆に、そのオブジェクトがスコープを抜けると、自動的にデストラクタが呼ばれます。変数の寿命がまだ曖昧な方は、ポインタの記事もあわせて読むと理解が深まります。

▼main.cpp


#include <iostream>

class Player {
public:
    Player() {
        std::cout << "コンストラクタが呼ばれました\n";
    }

    ~Player() {
        std::cout << "デストラクタが呼ばれました\n";
    }
};

int main() {
    Player player;
    std::cout << "mainの処理中です\n";
    return 0;
}

実行結果

コンストラクタが呼ばれました
mainの処理中です
デストラクタが呼ばれました

このように、作られた時にコンストラクタ、main関数を抜けて消える時にデストラクタが呼ばれています。

コンストラクタの基本的な使い方

実際には、メンバ変数の初期化に使うことがかなり多いです。

▼main.cpp


#include <iostream>

class Player {
public:
    int hp;

    Player() {
        hp = 100;
    }
};

int main() {
    Player player;

    std::cout << player.hp << "\n";
    return 0;
}

実行結果

100

このように、生成した瞬間にHPが100になるようにできます。毎回 player.hp = 100; と書かなくてよくなるので、かなりミスを減らせます。

デストラクタの基本的な使い方

デストラクタは、オブジェクトが消える時に実行したい後始末を書く場所です。代表的なのは、動的確保したメモリの解放やログ出力などです。

▼main.cpp


#include <iostream>

class Enemy {
public:
    Enemy() {
        std::cout << "敵を生成しました\n";
    }

    ~Enemy() {
        std::cout << "敵を破棄しました\n";
    }
};

int main() {
    {
        Enemy enemy;
        std::cout << "戦闘中です\n";
    }

    std::cout << "スコープを抜けました\n";
    return 0;
}

実行結果

敵を生成しました
戦闘中です
敵を破棄しました
スコープを抜けました

このように、スコープを抜けた瞬間にデストラクタが呼ばれます。つまり「いつ消えるか」は、変数の寿命と深く関係しています。

【重要】私が実際にコンストラクタとデストラクタで困った体験談

私も自主制作ゲームで、敵クラスのHPや座標の初期化をコンストラクタにまとめず、生成後にバラバラに代入していた時期がありました。すると、ある敵だけ初期化し忘れて、HPが変な値のまま出現することがありました。

そこで生成時の初期値をコンストラクタに集約したところ、かなり安定しました。また、破棄時のログをデストラクタに入れてみると、「思っていたタイミングで消えていない」オブジェクトを見つけやすくなりました。

ただし、敵が倒された時のエフェクト発生そのものをデストラクタに強く依存させる設計は、初心者のうちは少し注意が必要です。学習用としては分かりやすいですが、実務では「倒されたイベント」と「破棄処理」は分けた方が見通しが良いことも多いです。

コンストラクタ・デストラクタ使用時のよくある失敗例と対処法

1. 初期化をコンストラクタにまとめず、設定漏れする

  • 失敗例:生成後にHPや座標を毎回手で代入して、どこかで書き忘れる
  • 対処法:初期値が決まっているメンバはコンストラクタでまとめて初期化する

2. デストラクタは自分で呼ぶものだと思ってしまう

  • 失敗例:普通の自動変数なのに、明示的にデストラクタを呼ぼうとする
  • 対処法:基本的には寿命が終わる時に自動で呼ばれると理解する

3. 破棄された後のデータを使おうとする

  • 失敗例:スコープを抜けたオブジェクトの情報を後から使おうとする
  • 対処法:オブジェクトの寿命を意識し、必要なら所有関係や設計を見直す

実践例:敵が消える時の後始末を確認する

学習用として、敵が破棄されるタイミングを確認する例を見てみましょう。ここではデストラクタでログを出しています。

▼main.cpp


#include <iostream>

class Enemy {
public:
    Enemy() {
        std::cout << "敵を出現させました\n";
    }

    ~Enemy() {
        std::cout << "敵が消えたので後始末します\n";
    }
};

void BattleScene() {
    Enemy enemy;
    std::cout << "敵と戦っています\n";
}

int main() {
    BattleScene();
    std::cout << "バトル終了です\n";
    return 0;
}

実行結果

敵を出現させました
敵と戦っています
敵が消えたので後始末します
バトル終了です

このように、BattleScene() を抜けたタイミングでデストラクタが呼ばれています。もし「どのタイミングで破棄されたか分からない」という時は、Visual Studioでのデバッグ方法もかなり役立ちます。

注意点

  • コンストラクタは「生成時の初期化」、デストラクタは「破棄時の後始末」と整理すると分かりやすい
  • 初期値が必要なメンバは、できるだけコンストラクタでまとめる
  • デストラクタは便利ですが、ゲームロジックを詰め込みすぎると追いづらくなることがある
  • オブジェクトの寿命とスコープをセットで理解することが大切

まとめ

  • コンストラクタはオブジェクト生成時に自動で呼ばれる関数
  • デストラクタはオブジェクト破棄時に自動で呼ばれる関数
  • Player player; と書くと、生成後にコンストラクタが実行される
  • スコープを抜けるとデストラクタが呼ばれて後始末が行われる
  • 初心者はまず「初期化をまとめる」「寿命を意識する」を覚えるのがおすすめ

関連記事