C++入門|コンストラクタのオーバーロード

コンストラクタは、インスタンス誕生時の起動モード。引数なしで通常起動するのか、引数ありで戦闘力を指定して起動するのか、C++は呼び出し方で自動的に選び分けます。

C++では、同じ名前の関数でも、引数の数や型が違えば複数定義できます。
この仕組みをオーバーロードといいます。

そして、オーバーロードは通常のメンバ関数だけでなく、コンストラクタにも使えます。

コンストラクタは、クラスからインスタンスが生成されるときに自動的に呼び出される特別な関数です。
インスタンスの初期値を設定したり、起動メッセージを表示したりする役割を持ちます。

ドラゴンボール風に考えると、コンストラクタは修行装置を起動するときの初期設定モードです。

たとえば、何も指定しないで修行モードを起動する場合もあります。
一方で、最初から戦闘力を指定して修行モードを起動したい場合もあります。

このように、同じクラスでも「引数なしで起動する」「引数ありで起動する」という複数の生成方法を用意したいときに、コンストラクタのオーバーロードを使います。

new KiTrainingMode();
new KiTrainingMode(1200);

この2つは、どちらもKiTrainingModeクラスのインスタンスを生成しています。

しかし、呼び出し方が違います。

呼び出し方呼び出されるコンストラクタイメージ
new KiTrainingMode();KiTrainingMode();通常訓練モードで起動
new KiTrainingMode(1200);KiTrainingMode(int power);戦闘力1200を指定して起動

このように、引数の有無によって呼び出されるコンストラクタが変わります。

ただし、ここで大切な注意点があります。

引数付きコンストラクタを1つでも自分で定義すると、引数なしのデフォルトコンストラクタは自動では用意されません。

そのため、new KiTrainingMode(); のように引数なしで生成したい場合は、デフォルトコンストラクタも自分で定義する必要があります。

図:引数によって呼び出されるコンストラクタが変わる

この図が示していること

この図では、同じKiTrainingModeクラスでも、インスタンス生成時の引数によって呼び出されるコンストラクタが変わることを表しています。

引数なしで生成すると、KiTrainingMode()が呼び出されます。
int型の値を1つ渡して生成すると、KiTrainingMode(int power)が呼び出されます。

これがコンストラクタのオーバーロードです。

コンストラクタのオーバーロードとは

コンストラクタのオーバーロードとは、同じクラスの中に、引数の違うコンストラクタを複数用意することです。

たとえば、次のように書けます。

class KiTrainingMode {
public:
    KiTrainingMode();
    KiTrainingMode(int power);
};

どちらもコンストラクタなので、名前はクラス名と同じKiTrainingModeです。

しかし、引数が違います。

コンストラクタ引数役割
KiTrainingMode()なし初期値0で起動する
KiTrainingMode(int power)int型1つ指定された戦闘力で起動する

C++は、newするときに渡された引数を見て、どちらのコンストラクタを呼び出すかを判断します。

KiTrainingMode* mode1 = new KiTrainingMode();
KiTrainingMode* mode2 = new KiTrainingMode(1200);

mode1では引数がないため、KiTrainingMode()が呼び出されます。
mode2ではint型の引数1200があるため、KiTrainingMode(int power)が呼び出されます。

ドラゴンボール風に言えば、同じ修行装置でも、通常起動するか、最初から戦闘力を指定して起動するかを選べるということです。

デフォルトコンストラクタとは

引数を受け取らないコンストラクタを、デフォルトコンストラクタといいます。

KiTrainingMode();

デフォルトコンストラクタは、次のような呼び出しで使われます。

new KiTrainingMode();

このとき、引数を渡していないため、引数なしのコンストラクタが必要になります。

たとえば、修行装置を通常モードで起動し、戦闘力を0から始めたい場合に使えます。

KiTrainingMode::KiTrainingMode() : m_power(0) {
}

このように書くと、インスタンス生成時にm_powerが0で初期化されます。

引数付きコンストラクタとは

引数付きコンストラクタは、インスタンス生成時に値を受け取るコンストラクタです。

KiTrainingMode(int power);

たとえば、次のように生成できます。

new KiTrainingMode(1200);

この場合、1200という値がコンストラクタに渡されます。

KiTrainingMode::KiTrainingMode(int power) : m_power(power) {
}

このように書くと、m_powerに1200が設定された状態でインスタンスが生成されます。

ドラゴンボール風に言えば、修行装置を起動する瞬間に、最初の戦闘力を指定しておくようなものです。

引数付きコンストラクタを定義したときの注意点

ここが今回の一番大切なポイントです。

C++では、コンストラクタを1つも自分で定義していない場合、必要に応じて引数なしのデフォルトコンストラクタが用意されます。

しかし、引数付きコンストラクタを1つでも自分で定義すると、デフォルトコンストラクタは自動生成されません。

つまり、次のようなクラスを作った場合、

class KiTrainingMode {
public:
    KiTrainingMode(int power);
};

このクラスには、KiTrainingMode(int power)しかありません。

そのため、次のように引数ありで生成することはできます。

new KiTrainingMode(1200);

しかし、次のように引数なしで生成しようとするとエラーになります。

new KiTrainingMode();

なぜなら、KiTrainingMode()という引数なしのコンストラクタが定義されていないからです。

エラーになるサンプルプログラム

まずは、引数付きコンストラクタだけを定義したサンプルを見てみましょう。

このプログラムでは、KiTrainingMode(int power)だけを定義しています。
そのため、new KiTrainingMode(1200);は使えますが、new KiTrainingMode();は使えません。

プロジェクト/ファイル名: Chap5_04/ki_training_mode.h

#ifndef _KI_TRAINING_MODE_H_
#define _KI_TRAINING_MODE_H_

class KiTrainingMode {
public:
    // 引数付きコンストラクタ
    KiTrainingMode(int power);

private:
    // 起動時の戦闘力
    int m_power;
};

#endif // _KI_TRAINING_MODE_H_

プロジェクト/ファイル名: Chap5_04/ki_training_mode.cpp

#include "ki_training_mode.h"

// 引数付きコンストラクタ
KiTrainingMode::KiTrainingMode(int power) : m_power(power) {
}

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

#include "ki_training_mode.h"

int main(int argc, char** argv) {
    // 修行モードを指すポインタ変数を用意する
    KiTrainingMode* mode1;
    KiTrainingMode* mode2;

    // 引数付きコンストラクタを呼び出す
    mode1 = new KiTrainingMode(1200);

    // デフォルトコンストラクタを呼び出そうとしているが、
    // KiTrainingMode() が定義されていないためエラーになる
    mode2 = new KiTrainingMode();

    // 生成したインスタンスを消去する
    delete mode1;
    delete mode2;

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

このプログラムでは、次の行でエラーになります。

mode2 = new KiTrainingMode();

KiTrainingModeクラスには、引数なしのコンストラクタKiTrainingMode()が定義されていません。

定義されているのは、次の引数付きコンストラクタだけです。

KiTrainingMode(int power);

そのため、引数なしで生成しようとしても、C++は呼び出せるコンストラクタを見つけられません。

図:引数付きコンストラクタだけでは引数なし生成ができない

この図が示していること

この図では、引数付きコンストラクタだけを定義した場合、引数なしの生成ができないことを表しています。

KiTrainingMode(int power)があるため、new KiTrainingMode(1200);は使えます。
しかし、KiTrainingMode()がないため、new KiTrainingMode();はエラーになります。

引数なしでも生成したい場合は、デフォルトコンストラクタを自分で定義する必要があります。

なぜデフォルトコンストラクタが使えないのか

C++では、コンストラクタを自分でまったく定義していない場合、コンパイラが引数なしのコンストラクタを用意してくれることがあります。

しかし、自分でコンストラクタを1つでも定義すると、コンパイラは「このクラスの生成方法はプログラマーが自分で決めるのだな」と判断します。

そのため、引数付きコンストラクタだけを書いた場合、引数なしのコンストラクタは自動では作られません。

今回のクラスでは、次のコンストラクタだけがあります。

KiTrainingMode(int power);

この状態で、次のように書くと、

new KiTrainingMode();

C++は「引数なしで呼べるKiTrainingMode()がない」と判断します。

ドラゴンボール風にたとえるなら、修行装置に「戦闘力を指定して起動するモード」だけを登録している状態です。
通常起動モードを登録していないのに、通常起動しようとしているため、起動できないわけです。

エラーを避けるための修正版

引数なしでも生成したい場合は、デフォルトコンストラクタを明示的に定義します。

つまり、KiTrainingMode()とKiTrainingMode(int power)の両方を用意します。

プロジェクト/ファイル名: Chap5_04/ki_training_mode.h

#ifndef _KI_TRAINING_MODE_H_
#define _KI_TRAINING_MODE_H_

class KiTrainingMode {
public:
    // デフォルトコンストラクタ
    KiTrainingMode();

    // 引数付きコンストラクタ
    KiTrainingMode(int power);

    // 戦闘力を取得する
    int getPower();

private:
    // 起動時の戦闘力
    int m_power;
};

#endif // _KI_TRAINING_MODE_H_

プロジェクト/ファイル名: Chap5_04/ki_training_mode.cpp

#include "ki_training_mode.h"

// デフォルトコンストラクタ
KiTrainingMode::KiTrainingMode() : m_power(0) {
}

// 引数付きコンストラクタ
KiTrainingMode::KiTrainingMode(int power) : m_power(power) {
}

// 戦闘力を取得する
int KiTrainingMode::getPower() {
    return m_power;
}

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

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

using namespace std;

int main(int argc, char** argv) {
    // 修行モードを指すポインタ変数を用意する
    KiTrainingMode* mode1;
    KiTrainingMode* mode2;

    // デフォルトコンストラクタを呼び出す
    mode1 = new KiTrainingMode();

    // 引数付きコンストラクタを呼び出す
    mode2 = new KiTrainingMode(1200);

    // 起動時の戦闘力を表示する
    cout << "通常訓練モードの戦闘力:" << mode1->getPower() << endl;
    cout << "指定戦闘力モードの戦闘力:" << mode2->getPower() << endl;

    // 生成したインスタンスを消去する
    delete mode1;
    delete mode2;

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

実行結果

通常訓練モードの戦闘力:0
指定戦闘力モードの戦闘力:1200

この修正版では、デフォルトコンストラクタと引数付きコンストラクタの両方を定義しています。

そのため、次の2つの生成方法がどちらも使えます。

mode1 = new KiTrainingMode();
mode2 = new KiTrainingMode(1200);

mode1では、デフォルトコンストラクタが呼び出され、m_powerは0になります。
mode2では、引数付きコンストラクタが呼び出され、m_powerは1200になります。

コンストラクタの呼び出し分け

修正版では、2つのコンストラクタがあります。

KiTrainingMode();
KiTrainingMode(int power);

C++は、newするときに渡された引数を見て、呼び出すコンストラクタを選びます。

コード呼び出されるコンストラクタm_powerの値
new KiTrainingMode()KiTrainingMode()0
new KiTrainingMode(1200)KiTrainingMode(int power)1200

このように、同じKiTrainingModeクラスでも、生成時の書き方によって初期状態を変えられます。

これはとても便利です。

通常の初期状態でよい場合は、引数なしで生成します。
最初から値を指定したい場合は、引数付きで生成します。

コンストラクタのオーバーロードで押さえるべき注意点

コンストラクタのオーバーロードでは、次の点をしっかり押さえておきましょう。

注意点内容
引数の違いで区別される引数なし、int型1つなどで呼び出し先が変わる
戻り値は書かないコンストラクタには戻り値の型を書かない
デフォルトコンストラクタは引数なしnew KiTrainingMode()で呼ばれる
引数付きコンストラクタを定義すると注意引数なしコンストラクタは自動生成されない
引数なし生成もしたい場合KiTrainingMode()を明示的に定義する

特に大切なのは、次のルールです。

引数付きコンストラクタを1つでも定義した場合、引数なしのデフォルトコンストラクタは自動では作られません。

そのため、次の両方を使いたいなら、

new KiTrainingMode();
new KiTrainingMode(1200);

クラスには次の両方を定義しておく必要があります。

KiTrainingMode();
KiTrainingMode(int power);

図:両方の生成方法を使いたいなら両方定義する

この図が示していること

この図では、コンストラクタの定義と呼び出し方の対応関係を表しています。

KiTrainingMode()を定義していれば、new KiTrainingMode()が使えます。
KiTrainingMode(int power)を定義していれば、new KiTrainingMode(1200)が使えます。

逆に、定義していない呼び出し方は使えません。

エラーになるコードと正しいコードの比較

エラーになるコードと正しいコードを比較すると、違いが分かりやすくなります。

エラーになるクラス

class KiTrainingMode {
public:
    KiTrainingMode(int power);
};

このクラスでは、次の呼び出しはできます。

new KiTrainingMode(1200);

しかし、次の呼び出しはできません。

new KiTrainingMode();

正しく両方使えるクラス

class KiTrainingMode {
public:
    KiTrainingMode();
    KiTrainingMode(int power);
};

このクラスでは、次の両方が使えます。

new KiTrainingMode();
new KiTrainingMode(1200);

つまり、クラスをどのような形で生成したいのかを考えて、必要なコンストラクタを用意することが大切です。

コンストラクタのオーバーロードで広がる初期化の自由度

コンストラクタをオーバーロードすると、インスタンス生成時の初期化パターンを複数用意できます。

たとえば、修行モードを表すクラスなら、次のような設計が考えられます。

コンストラクタ初期化内容
KiTrainingMode()戦闘力0で通常起動
KiTrainingMode(int power)指定した戦闘力で起動
KiTrainingMode(int power, int gravity)戦闘力と重力倍率を指定して起動

このように、引数を増やすことで、より細かい初期設定ができるようになります。

ただし、増やしすぎると、どのコンストラクタを使えばよいのか分かりにくくなります。

そのため、必要な初期化パターンだけを用意するのが大切です。

ドラゴンボール風に言えば、修行装置に起動モードをたくさん作りすぎると、操作パネルが複雑になってしまいます。
よく使う起動方法を整理して、分かりやすく用意するのが良い設計です。

このテーマで押さえたいポイント

コンストラクタのオーバーロードでは、次のポイントを理解しておきましょう。

ポイント内容
コンストラクタもオーバーロードできる引数の型や数を変えて複数定義できる
引数なしコンストラクタデフォルトコンストラクタと呼ぶ
引数付きコンストラクタ初期値を受け取ってインスタンスを生成できる
呼び出し分けnewするときの引数によって決まる
注意点引数付きコンストラクタを定義すると、デフォルトコンストラクタは自動生成されない
解決方法引数なしでも生成したいなら、デフォルトコンストラクタも明示的に定義する

コンストラクタのオーバーロードを使うと、同じクラスでも複数の初期化方法を用意できます。

通常訓練モードで起動したいときは、引数なしで生成します。
最初から戦闘力を指定したいときは、引数付きで生成します。

ただし、引数付きコンストラクタを作った場合、引数なしの生成方法は自動では用意されません。

このルールを忘れると、new KiTrainingMode(); のようなコードでコンパイルエラーになります。

C++では、インスタンスをどのように誕生させたいのかを、コンストラクタとしてはっきり定義しておくことが大切です。

ドラゴンボール風に言えば、修行装置に「通常起動モード」と「指定戦闘力起動モード」の両方を使わせたいなら、両方の起動設定を用意しておく必要があります。

どの生成方法を許可するのかをクラス側で決める。
それが、コンストラクタのオーバーロードを安全に使うための大切な考え方です。

次に続けるなら、オーバーライドとvirtual関数へつなげると、ポリモーフィズムの理解がかなり深まります。