DEV Community

Hashi
Hashi

Posted on • Edited on

【C++】コンストラクタとディストラクタをマスターしよう

目次

  1. コンストラクタ →イニシャリゼーションリスト デリゲート
  2. ディストラクタ
  3. コンストラクタの応用: コピーコンストラクタ

コンストラクターはオブジェクト指向プログラミング(OOP)において最も重要な要素の1つですが、便利な反面、機能が豊富な分、いざ使おうとしてみると混乱しやすい面もあります

特にC++のフレームワークにおいて、コンストラクタメソッドにかなりの量のコードが記述されがちなので、ここで完璧に抑えておきたいところです


(重要)コンストラクタを学習する際に一番重要なのは、デバッグをして処理の順序を目で追うことです。特にのちに紹介するデリゲートを使用する際には、デバッガーは必須のツールですので、この機会に使う癖をつけましょう


コンストラクタ・ディストラクタは特殊なメソッド

まず、2つのメソッドは特殊なメンバーメソッドであるということ

具体的には主に以下のような性質があります

  1. メソッド宣言時に戻り値の型は書かない
  2. 特定のタイミングにおいて自動で実行される
  3. クラス実装時に宣言しない場合、コンパイラがデフォルトを実行する

上の3点をカバーしながら解説していきます


1, コンストラクタ

コンストラクタとはオブジェクトのイニシャライズを行うメソッドです

例えばクラス名がStudentの場合、コンストラクタは

Student(){}; というように実装します

なお、上の引数・ボディが空の状態がデフォルトであり、コンストラクタをプログラマが実装しなかった場合は、コンパイラはこのデフォルトを自動で実装します

なので

#include <string>
#include<iostream>

using namespace std;

class Student{
public:
    // メソッドの実装
    Student(){
        // Body
        cout << "No args constructor called" << endl;
    };

};

int main() {
    Student rei = Student();

    return 0;
}
Enter fullscreen mode Exit fullscreen mode

よって、上の場合はコンストラクタの記述を消しても正常に動きますね(インスタンス作成のところでブレークポイントを置いてデバッグして確認してみましょう)


イニシャリゼーションリスト

ここで最も基本的なオブジェクトの属性のイニシャライズを実装してみます

#include <string>
#include<iostream>

using namespace std;

class Player{
public:
    // 属性
    string name;
    int health;

    // メソッドの実装 
    // デフォルトのコンストラクタを記述しておくこと
    Player(){
        cout << "No args constructor called" << endl;
    };
    // コンストラクタのカスタマイズ
    Player(string s, int num): name{s}, health{num}{
        cout << "Two args constructor called" << endl;
    };

    void showStatus()
    {
        //name と healthをプリント
        cout << "name: " << name << " "<<"health: " << health << endl;
    }

};

int main() {
    Player kiku = Player("Kiku", 100);
    Player rei = Player(); //デフォルトのコンストラクタを記述していなければエラー

    kiku.showStatus();    


    return 0;
}
Enter fullscreen mode Exit fullscreen mode

今回はPlayerオブジェクトのイニシャライズ時にname, healthの2つの属性を設定するために、インスタンス生成時に2つの引数を持つコンストラクタを実装します

ここでポイントがあります

それは、オブジェクトの属性のイニシャライズはコンストラクタのbodyではなく、関数の引数の()の直後において行っています

ここでの処理をイニシャリゼーションリストと呼び、コンストラクタのbodyが実行される前にこのイニシャリゼーションリストが実行されます

文法→ : 各属性名{適切なコンストラクタの引数名} となります

ここでまた注意するべきことは、一度デフォルトでないコンストラクタを作成した場合は、これまでコンパイラが自動で実装していたデフォルトのコンストラクタを自身で実装する必要があるということです

それゆえに、上記では2つのコンストラクタが実装されていることがわかりますね


デリゲート

ここまで読んできて、コンストラクタの柔軟性から様々な引数を持ったコンストラクタを実装することができることが分かりました

そこで、以下のコンストラクタの実装を見てみましょう

    // コンストラクタのカスタマイズ
    Player(): name{"None"}, health{0}{
        cout << "No args constructor called" << endl;
    };
    Player(string s): name{s}, health{0}{
        cout << "One arg constructor called" << endl;
    };
    Player(string s, int num): name{s}, health{num}{
        cout << "Two args constructor called" << endl;
    };
Enter fullscreen mode Exit fullscreen mode

イニシャリゼーションリストの部分を見てみると、name{}, health{} という記述が重複していることがわかりますね

ここでデリゲート(委託)を用いるとかなりスッキリしたコードになります

デリゲートとは名前の通り、コンストラクタが特定のコンストラクタで実装されたイニシャリゼーションリストに委託する形でイニシャライズを行います

デリゲート元における文法→ : クラス名{ ここに被デリゲートのイニシャリゼーションリストの属性の順序に沿って引数を置いていく }

つまり、デリゲート元と被デリゲートの関係が生まれ、この時にはイニシャライズは被デリゲートのコンストラクタが実行された後にデリゲート元のコンストラクタのbodyが実行されるという処理順序になります

実際にデリゲートを実装してみましょう(必ず自身でデバッグして処理の順序を確認してください)

    // コンストラクタのカスタマイズ
    Player(): Player{"None", 0}{
        cout << "No args constructor called" << endl;
    };
    Player(string s): Player{s, 0}{
        cout << "One arg constructor called" << endl;
    };
    Player(string s, int num): name{s}, health{num}{
        cout << "Two args constructor called" << endl;
    };
Enter fullscreen mode Exit fullscreen mode

上の2つがデリゲート元、一番下が被デリゲートになります


2, ディストラクタ

ディストラクタはコンストラクタとは対照的に、オブジェクトがスコープ外に達した時(削除される)にコンパイラによって自動で実行され、メモリが解放されます

また、ディストラクタはカスタマイズ可能なコンストラクタとは異なり、引数を持てず、1つのクラスにつきディストラクタらは1つだけ

オーバーロードは不可能です

コンストラクタと共通しているのは、デフォルトでディストラクタもコンパイラによって自動で実装されます


3, コンストラクタの応用

ここからは、オブジェクトを引数にもつ関数やオブジェクトをヒープに格納するポインタを扱う際に必須となってくるコピーコンストラクタを別記事にて紹介します

コピーコンストラクタの記事

Top comments (0)