🖥️

【C++言語入門】 第4回 コンストラクタとデストラクタ

2025/02/18に公開

https://youtu.be/3CRNrmTDOX0

四国めたん
\textcolor{pink}{四国めたん: }教師役ですわ

ずんだもん
\textcolor{lime}{ずんだもん: }生徒役なのだ

\footnotesize \textcolor{pink}{四国めたん:} こんにちは。四国めたんです

\footnotesize \textcolor{lime}{ずんだもん:} ずんだもんなのだ。こんにちはなのだ

\footnotesize \textcolor{pink}{四国めたん:} 今回は コンストラクタデストラクタ についてお話ししますわ

\footnotesize \textcolor{lime}{ずんだもん:} コンストラクタデストラクタ

\footnotesize \textcolor{pink}{四国めたん:} はい、 コンストラクタ はクラスのインスタンスを生成する際に呼ばれるメソッドですわ

\footnotesize \textcolor{pink}{四国めたん:} そして デストラクタ はクラスのインスタンスを破棄する際に呼ばれるメソッドですわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほど、さっそくお願いするのだ

クラスに初期化の処理を含めましょう

\footnotesize \textcolor{pink}{四国めたん:} 前回は、クラスに対してデータだけではなく、 メソッド として関数を含むことができることをお話ししましたわ

\footnotesize \textcolor{lime}{ずんだもん:} おぼえているのだ

\footnotesize \textcolor{pink}{四国めたん:} しかし、メンバ変数の初期化については、クラスの外から、例えばメイン関数でセッター等を用いておこなっていますわ

\footnotesize \textcolor{lime}{ずんだもん:} その通りなのだ

\footnotesize \textcolor{pink}{四国めたん:} ただ、メンバ変数の数が多くなってくると、セッターを使用した初期化は負担が多くなると思いませんか?

\footnotesize \textcolor{lime}{ずんだもん:} たしかに...

\footnotesize \textcolor{pink}{四国めたん:} ですのでメンバ変数の数が多い場合には、Initのようなメソッドを用意して、一括して初期化を行う方が効率的かと思いますわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

初期化用のメソッドは便利ですよ

\footnotesize \textcolor{pink}{四国めたん:} とりあえず、前回のプログラム例をInitメソッドによる初期化で書き換えてみますわ

\footnotesize \textcolor{lime}{ずんだもん:} メンバ変数の数は1つだけなのだが...

\footnotesize \textcolor{pink}{四国めたん:} それではメンバ変数を追加してみますわ

#include <iostream>

#define PI (3.14159265358979323846)

/// @brief 円
class Circle {
  double diameter_;      // 直径
  double border_width_;  // 境界線の幅

 public:
  double Diameter() { return diameter_; }
  void Diameter(double diameter) { diameter_ = diameter; }
  double BorderWidth() { return border_width_; }
  void BorderWidth(double border_width) { border_width_ = border_width; }

  void Init(double diameter, double border_width) {
    Diameter(diameter);
    BorderWidth(border_width);
    return;
  }

  double Area() {
    double radius = Diameter() / 2.0;
    double a = radius * radius * PI;
    return a;
  }
};

int main(int argc, char* argv[]) {
  Circle c;
  c.Init(10.0, 1.0);
  double area = c.Area();
  std::cout << "円の直径は" << c.Diameter() << "cmです。" << std::endl;
  std::cout << "円の境界線の幅は" << c.BorderWidth() << "です。" << std::endl;
  std::cout << "円の面積は" << area << "です。" << std::endl;
  return 0;
}

初期化のメソッドを使った結果

\footnotesize \textcolor{lime}{ずんだもん:} とりあえず初期化はできているようなのだ

\footnotesize \textcolor{pink}{四国めたん:} まぁ、メンバ変数が2つ程度でしたら、セッターを使用した初期化と比べて便利には思えませんわね

\footnotesize \textcolor{pink}{四国めたん:} でもメンバ変数の数が多かったり、ポインタだった場合などは、初期化のメソッドはかなり便利ですわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

インスタンス生成時にコンストラクタが呼ばれます

\footnotesize \textcolor{pink}{四国めたん:} さて、初期化のメソッドによりインスタンスの初期化が簡便になることをお話ししましたわ

\footnotesize \textcolor{lime}{ずんだもん:} メンバ変数の数が少ないと恩恵を感じられないのだ

\footnotesize \textcolor{pink}{四国めたん:} しかしクラスには更に一歩進んで、インスタンス生成と同時に実行される コンストラクタ と呼ばれる特別なメソッドが決められていますわ

\footnotesize \textcolor{lime}{ずんだもん:} コンストラクタ

\footnotesize \textcolor{pink}{四国めたん:} はい、定義や宣言の方法も独特ですわね

クラス名(引数, ...)
  : 初期化子リスト {
  初期化の処理
}

\footnotesize \textcolor{pink}{四国めたん:} まず、 コンストラクタ のメソッド名は、 クラス名 をそのまま使用しますわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほど、判り易いのだ

\footnotesize \textcolor{pink}{四国めたん:} 戻り値はなしですわ

\footnotesize \textcolor{lime}{ずんだもん:} voidでもないのか?

\footnotesize \textcolor{pink}{四国めたん:} はい、型の指定もできませんわ

\footnotesize \textcolor{lime}{ずんだもん:} かなり特殊な形式なのだ

\footnotesize \textcolor{pink}{四国めたん:} そしてクラス内のメンバ変数は、まず、コロン":"の後の 初期化子リスト に記述された値により初期化されますわ

\footnotesize \textcolor{lime}{ずんだもん:} 初期化子リスト

\footnotesize \textcolor{pink}{四国めたん:} はい、 初期化子リスト はメンバ変数の後に初期値を括弧で括って指定しますわ

\footnotesize \textcolor{pink}{四国めたん:} それをカンマ区切りでリストアップする形になりますわね

メンバ変数1(初期値1), メンバ変数2(初期値2), ...

\footnotesize \textcolor{lime}{ずんだもん:} 初期値には定数をセットするのか?

\footnotesize \textcolor{pink}{四国めたん:} 定数もセットできますが、 コンストラクタ の引数を指定することも可能ですわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

\footnotesize \textcolor{pink}{四国めたん:} そして、 初期化子リスト で初期化できないような処理、例えばヒープからメモリ領域を割り当てるような処理については、 コンストラクタ の処理内で行いますわ

\footnotesize \textcolor{lime}{ずんだもん:} りょうかいなのだ

\footnotesize \textcolor{pink}{四国めたん:} それでは実際の コンストラクタ を使ったプログラムを見てみましょう

#include <iostream>

#define PI (3.14159265358979323846)

/// @brief 円
class Circle {
  double diameter_;      // 直径
  double border_width_;  // 境界線の幅

 public:
  Circle(): Circle(0.0, 0.0) {}
  Circle(double diameter, double border_width)
      : diameter_(diameter), border_width_(border_width) {}

  double Diameter() { return diameter_; }
  void Diameter(double diameter) { diameter_ = diameter; }
  double BorderWidth() { return border_width_; }
  void BorderWidth(double border_width) { border_width_ = border_width; }

  double Area() {
    double radius = Diameter() / 2.0;
    double a = radius * radius * PI;
    return a;
  }
};

int main(int argc, char* argv[]) {
  Circle c(10.0, 1.0);
  double area = c.Area();
  std::cout << "円の直径は" << c.Diameter() << "cmです。" << std::endl;
  std::cout << "円の境界線の幅は" << c.BorderWidth() << "です。" << std::endl;
  std::cout << "円の面積は" << area << "です。" << std::endl;
  return 0;
}

コンストラクタを使った結果

\footnotesize \textcolor{lime}{ずんだもん:} とりあえず初期化はできているようなのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、Circleという名前のメソッドが コンストラクタ ですわ

\footnotesize \textcolor{lime}{ずんだもん:} Circleと云う名のメソッドは2つあるのだ

\footnotesize \textcolor{pink}{四国めたん:} 今回は引数のある コンストラクタ と引数のない デフォルトのコンストラクタ を追加していますわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

\footnotesize \textcolor{pink}{四国めたん:} ゲッター、セッターと同様に、同じ名前の関数となっていますが、詳細は後の回でお話ししますわ

\footnotesize \textcolor{pink}{四国めたん:} 今は「C++言語では許されている」としてスルーしておいてください

\footnotesize \textcolor{lime}{ずんだもん:} まっているのだ

\footnotesize \textcolor{pink}{四国めたん:} ちなみに引数があるコンストラクタでは 初期化子リスト でメンバ変数を初期化していますわ

\footnotesize \textcolor{lime}{ずんだもん:} "diameter_"メンバ変数には引数の"diameter"を、"border_width_"メンバ変数には引数の"border_width"を割り当てているのだ

\footnotesize \textcolor{pink}{四国めたん:} そうですわね

\footnotesize \textcolor{lime}{ずんだもん:} ところで デフォルトのコンストラクタ では 初期化子リスト で、引数のある コンストラクタ を呼んでいるのだ

\footnotesize \textcolor{pink}{四国めたん:} 気づきました?

\footnotesize \textcolor{pink}{四国めたん:} このように、 初期化子リスト 中で他の コンストラクタ を呼び出して初期化することも可能ですわ

\footnotesize \textcolor{lime}{ずんだもん:} ふむふむ

\footnotesize \textcolor{pink}{四国めたん:} 今回の コンストラクタ では、他に初期化の処理は必要ないので、処理の部分はカラとなっていますわね

\footnotesize \textcolor{lime}{ずんだもん:} ちなみに コンストラクタInitなどのメソッドを呼び出すことはできるのか?

\footnotesize \textcolor{pink}{四国めたん:} 初期化子リスト の部分ではできませんが、処理の部分であれば可能ですわ

\footnotesize \textcolor{pink}{四国めたん:} 次にメイン関数内でCircleのインスタンス"c"を宣言、初期化する際には、関数コールと同様に括弧内に引数を指定しますわ

\footnotesize \textcolor{lime}{ずんだもん:} 指定した引数に合った コンストラクタ が呼ばれるのだ

\footnotesize \textcolor{pink}{四国めたん:} その通りですわ

デフォルトのコンストラクタって必要?

プログラム例では、 デフォルトのコンストラクタ として、引数のない コンストラクタ を定義しています

実はC++言語では、クラスのインスタンスが生成される際は必ず コンストラクタ が呼ばれます

その際に呼ばれるのは、指定された引数に合った コンストラクタ です

妥当な コンストラクタ がない場合には、 エラー となります

なお デフォルトのコンストラクタ は、引数を指定しないでインスタンスを生成した際に呼び出される コンストラクタ です

なので、たとえカラのメソッドだとしても、 デフォルトのコンストラクタ を定義しないと、Circle c;というような宣言はエラーとなります

「今までのプログラム例では、デフォルトのコンストラクタを定義していなくてもエラーとはなっていない」と言われるかもしれません

実はC++言語では、 コンストラクタ を1つも定義されていないクラスは、 デフォルトのコンストラクタ を自動で生成してくれるようになっているのです

今までのプログラム例では、 コンストラクタ を1つも持っていなかったため、自動で デフォルトのコンストラクタ が生成されていたのでエラーとなっていませんでした

今回のプログラム例では、引数を持った コンストラクタ を定義したため、自動で デフォルトのコンストラクタ が生成されなくなりましたので、明示的に作成しています

なお、引数を指定せずにインスタンスを作成する必要がないクラスの場合には、 デフォルトのコンストラクタ は必要ありません

むしろ デフォルトのコンストラクタ を作らなければ、意図しない初期化の値でインスタンスを生成されるのを防止できます

クラスには後始末も必要ですよ

\footnotesize \textcolor{pink}{四国めたん:} これまで コンストラクタ についてお話ししてきましたが、次は デストラクタ についてお話ししますわ

\footnotesize \textcolor{lime}{ずんだもん:} デストラクタ

\footnotesize \textcolor{pink}{四国めたん:} はい、 コンストラクタ がインスタンス生成時に呼ばれるのとは反対に、 デストラクタ はインスタンス消滅時に呼ばれるメソッドですわ

\footnotesize \textcolor{lime}{ずんだもん:} イメージがわかないのだ

\footnotesize \textcolor{pink}{四国めたん:} 例えば、関数内で生成されたインスタンスは、関数を抜ける際に破棄されますわ

\footnotesize \textcolor{lime}{ずんだもん:} その通りなのだ

\footnotesize \textcolor{pink}{四国めたん:} その際に呼ばれるのが デストラクタ ですわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

\footnotesize \textcolor{lime}{ずんだもん:} でも、インスタンス生成時には初期化など、いろいろと必要な処理があるのはわかるが、消滅時に必要な処理はあるのか?

\footnotesize \textcolor{pink}{四国めたん:} 実は結構あって、例えば初期化時にファイルをオープンしたり、メモリを動的に確保した場合などでは必要ですわ

\footnotesize \textcolor{lime}{ずんだもん:} そうなのか?

\footnotesize \textcolor{pink}{四国めたん:} はい、その場合は、ファイルのクローズやメモリの開放などを デストラクタ で行わなければなりませんわ

\footnotesize \textcolor{lime}{ずんだもん:} りょうかいなのだ

\footnotesize \textcolor{pink}{四国めたん:} ちなみに デストラクタ の定義はこのようにしますわ

~クラス名() { 処理 }

\footnotesize \textcolor{lime}{ずんだもん:} デフォルトのコンストラクタ のように見えるのだ

\footnotesize \textcolor{pink}{四国めたん:} 似たように見えますが、 デストラクタ はクラス名の前にチルド"~"を付加しますわ

\footnotesize \textcolor{lime}{ずんだもん:} お~、本当なのだ

\footnotesize \textcolor{pink}{四国めたん:} ちなみに コンストラクタ と同じく戻り値はありませんわ

\footnotesize \textcolor{lime}{ずんだもん:} 引数もないのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、 デストラクタ は明示的に呼ばれませんので、引数はありませんわ

\footnotesize \textcolor{lime}{ずんだもん:} コンストラクタ のように複数の デストラクタ を定義することはできるのか?

\footnotesize \textcolor{pink}{四国めたん:} いいえ、複数の デストラクタ を定義することもできませんわね

\footnotesize \textcolor{lime}{ずんだもん:} りょうかいなのだ

\footnotesize \textcolor{pink}{四国めたん:} それではメモリを動的に確保する場合の例を見てみましょう

\footnotesize \textcolor{lime}{ずんだもん:} お願いするのだ

#include <iostream>

#define PI (3.14159265358979323846)
#define MESSAGE_SIZE (50)

/// @brief 円
class Circle {
  double diameter_;      // 直径
  double border_width_;  // 境界線の幅
  char* pmessage_;

 public:
  Circle(double diameter, double border_width)
      : diameter_(diameter), border_width_(border_width), pmessage_(nullptr) {
    pmessage_ = (char*)malloc(MESSAGE_SIZE);
  }
  ~Circle() {
    std::cout << "デストラクタが呼ばれました" << std::endl;
    free(pmessage_);
    pmessage_ = nullptr;
  }

  double Diameter() { return diameter_; }
  void Diameter(double diameter) { diameter_ = diameter; }
  double BorderWidth() { return border_width_; }
  void BorderWidth(double border_width) { border_width_ = border_width; }

  double Area() {
    double radius = Diameter() / 2.0;
    double a = radius * radius * PI;
    return a;
  }

  const char* Message() {
    double area = Area();
    sprintf_s(pmessage_, MESSAGE_SIZE, "円の面積は%fです。", area);
    return pmessage_;
  }
};

int main(int argc, char* argv[]) {
  Circle c(10.0, 1.0);
  std::cout << "円の直径は" << c.Diameter() << "cmです。" << std::endl;
  std::cout << c.Message() << std::endl;
  return 0;
}

デストラクタを使った結果

\footnotesize \textcolor{lime}{ずんだもん:} 円の面積を表示した後に「デストラクタが呼ばれました」と表示されたのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、Messageメソッドで取得した文字列を表示した後に デストラクタ が呼ばれているのがわかりますわ

\footnotesize \textcolor{lime}{ずんだもん:} プログラムの説明をお願いするのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、まずCircleクラスのメンバ変数として、文字列を格納するためのメモリ領域へのポインタ"pmessage_"を追加していますわ

\footnotesize \textcolor{pink}{四国めたん:} そして、 コンストラクタ の処理部分でmallocにより50文字分のメモリ領域を確保していますわ

\footnotesize \textcolor{lime}{ずんだもん:} 初期化子リスト では"pmessage_"をnullptrで初期化しているのだ

\footnotesize \textcolor{pink}{四国めたん:} コンストラクタ の処理部分で値を指定しているので必要ないのですが、念のためですわ

\footnotesize \textcolor{lime}{ずんだもん:} ふむ

\footnotesize \textcolor{lime}{ずんだもん:} ところでmallocfreesprintf_sを使っているのに"string.h"や"stdio.h"をインクルードしていないのだが...

\footnotesize \textcolor{pink}{四国めたん:} "cstring"や"cstdio"ですわね

\footnotesize \textcolor{pink}{四国めたん:} それらは"iostream"内でインクルードしているので明示的にインクルードする必要はありませんわ

\footnotesize \textcolor{lime}{ずんだもん:} りょうかいなのだ

\footnotesize \textcolor{pink}{四国めたん:} ちなみに デフォルトのコンストラクタ は必要ないので削除していますわ

\footnotesize \textcolor{lime}{ずんだもん:} ふむふむ

\footnotesize \textcolor{pink}{四国めたん:} そして、円の面積を伝えるメッセージを作成するメソッドMessageを追加していますわ

\footnotesize \textcolor{lime}{ずんだもん:} Messageメソッドの中ではsprintf_s関数を使用して"pmessage_"のメモリ領域に文字列を作成しているのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、そして作成した文字列をMessageメソッドの戻り値としていますわね

\footnotesize \textcolor{lime}{ずんだもん:} ところで、なぜ戻り値の型をconstで修飾しているのだ?

\footnotesize \textcolor{pink}{四国めたん:} constを付加すると、戻り値として得られた"pmessage_"の内容を書き換えることができないようにできるからですわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほど、クラスの外でメンバ変数の内容を書き換えられないことを保証できるのか

\footnotesize \textcolor{pink}{四国めたん:} その通りですわ

\footnotesize \textcolor{pink}{四国めたん:} そしてメイン関数内でメッセージを表示した後、「デストラクタが呼ばれました」と表示されていますわね

\footnotesize \textcolor{lime}{ずんだもん:} メイン関数の最後にCircleクラスの デストラクタ が呼ばれていることがわかるのだ

まとめ

\footnotesize \textcolor{pink}{四国めたん:} 以上で コンストラクタとデストラクタ の説明を終わりますわ

\footnotesize \textcolor{lime}{ずんだもん:} おつかれさまなのだ

\footnotesize \textcolor{pink}{四国めたん:} お疲れさまでした

Discussion