🖥️

【C++言語入門】 第9回 継承

2025/03/01に公開

https://youtu.be/UVuCwPydGP0

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

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

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

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

\footnotesize \textcolor{pink}{四国めたん:} 今回はクラスの 継承 についてお話ししますわ

\footnotesize \textcolor{lime}{ずんだもん:} 継承

\footnotesize \textcolor{pink}{四国めたん:} クラスの 継承 はC++言語の重要な機能の1つですわ

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

\footnotesize \textcolor{pink}{四国めたん:} はい、さっそくその辺りをお話ししますわね。

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

他のクラスから全てを引継ぎましょう

\footnotesize \textcolor{pink}{四国めたん:} さて、今まで クラス について、ある程度の解説をしてきましたわ

\footnotesize \textcolor{lime}{ずんだもん:} 結構、いろいろとあったのだ

\footnotesize \textcolor{pink}{四国めたん:} 今回は クラス の最大の特徴とも云える 継承 もしくは Inheritance についてお話ししますわ

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

\footnotesize \textcolor{pink}{四国めたん:} ですが、その前にクラスの機能を拡張する一般的な方法をお話ししますわ

\footnotesize \textcolor{pink}{四国めたん:} 例えば、まずCircleというクラスが既に存在したとしますわ

\footnotesize \textcolor{lime}{ずんだもん:} それで?

\footnotesize \textcolor{pink}{四国めたん:} ここで新たにEllipse(楕円)というクラスを作成したいと思いますわ

\footnotesize \textcolor{lime}{ずんだもん:} Circleとは別に新しく作れば良いのではないか?

\footnotesize \textcolor{lime}{ずんだもん:} Circleクラス程度の分量であれば、大した手間もかからないのだ

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

\footnotesize \textcolor{pink}{四国めたん:} ただ、もしCircleクラスの分量が膨大で、新たに作成するEllipseクラスも膨大な量のコードを書かなければならないとすればどうでしょうか?

\footnotesize \textcolor{lime}{ずんだもん:} かなり大変なのだ

\footnotesize \textcolor{pink}{四国めたん:} Circleクラスを拡張する形でEllipseクラスを作成することができれば素晴らしいと思いませんか?

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

\footnotesize \textcolor{pink}{四国めたん:} すぐに思いつく方法はEllipseクラスのメンバ変数としてCircleクラスのインスタンスを使う方法ですわ

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

\footnotesize \textcolor{pink}{四国めたん:} とりあえず実例を見てみましょう

circle.h
#pragma once

#ifndef CIRCLE_H
#define CIRCLE_H

#include <iostream>

const double kPI = 3.14159265358979323846;

/// @brief 円
class Circle {
  double diameter_;  // 直径

 public:
  Circle(double diameter) : diameter_(diameter) {}
  ~Circle() {}

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

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

#endif  // CIRCLE_H
ellipse.h
#pragma once

#ifndef ELLIPSE_H
#define ELLIPSE_H

#include "circle.h"

/// @brief 楕円
class Ellipse {
  Circle circle_;         // 円(短径)
  double long_diameter_;  // 長径

 public:
  Ellipse(double short_diameter, double long_diameter)
      : circle_(short_diameter), long_diameter_(long_diameter) {}
  ~Ellipse() {}

  double LongDiameter() { return long_diameter_; }
  void LongDiameter(double long_diameter) { long_diameter_ = long_diameter; }
  double ShortDiameter() { return circle_.Diameter(); }
  void ShortDiameter(double short_diameter) {
    circle_.Diameter(short_diameter);
  }

  double Area() {
    double short_radius = ShortDiameter() / 2.0;
    double long_radius = LongDiameter() / 2.0;
    double a = short_radius * long_radius * kPI;
    return a;
  }
};

#endif  // ELLIPSE_H
hello_world.cpp
#include <iostream>

#include "ellipse.h"

int main(int argc, char* argv[]) {
  Ellipse* pe = new Ellipse(10.0, 15.0);
  std::cout << "楕円の長径は" << pe->LongDiameter() << "cmです。" << std::endl;
  std::cout << "楕円の短径は" << pe->ShortDiameter() << "cmです。" << std::endl;
  std::cout << "楕円の面積は" << pe->Area() << "cm^2です。" << std::endl;
  delete pe;
  pe = nullptr;
  return 0;
}

楕円の結果

\footnotesize \textcolor{lime}{ずんだもん:} 楕円の長径と短径、面積を表示できたのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、今回の例ではCircleクラスのコンストラクタとデストラクタ以外に、直径を表すdiameter_メンバ変数と、そのゲッターとセッター、そして面積を計算するAreaメソッドのみとしましたわ

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

\footnotesize \textcolor{pink}{四国めたん:} はい、"pmessage_"メンバ変数などは削除しましたわ

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

\footnotesize \textcolor{pink}{四国めたん:} そして新たにEllipseクラスを別ファイルとして用意しましたわ

\footnotesize \textcolor{lime}{ずんだもん:} クラス作成手順は前前回、説明してもらったのだ

\footnotesize \textcolor{pink}{四国めたん:} そしてEllipseクラスは楕円を表すために直径と短径を必要としますわ

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

\footnotesize \textcolor{pink}{四国めたん:} なお、短径の方をCircleクラスのインスタンスが担うように、メンバ変数"circle_"を設けていますわ

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

\footnotesize \textcolor{pink}{四国めたん:} そして長径には素直に"long_diameter_"メンバ変数を設けましたわ

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

Ellipseのコンストラクタ

\footnotesize \textcolor{pink}{四国めたん:} ところでEllipseのコンストラクタでは、引数に長径と短径を指定するようになっていますわ

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

\footnotesize \textcolor{pink}{四国めたん:} 長径については初期化子リスト中で"long_diameter_"の初期値として設定していますわ

\footnotesize \textcolor{pink}{四国めたん:} 短径についても初期化子リスト中で"circle_"の初期値として設定していますわ

\footnotesize \textcolor{lime}{ずんだもん:} つまり、短径についてはCircleのコンストラクタの引数として渡されて"diameter_"メンバ変数の初期値になるのか?

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

Ellipseの面積を計算しよう

\footnotesize \textcolor{pink}{四国めたん:} ところで楕円の面積の計算方法は覚えていますか?

\footnotesize \textcolor{lime}{ずんだもん:} 長半径と短半径をそれぞれar, brとした場合の楕円の面積はこんな感じだったのだ

S = \pi \cdot ar \cdot br

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

\footnotesize \textcolor{lime}{ずんだもん:} 簡単なのだ

\footnotesize \textcolor{pink}{四国めたん:} EllipseクラスのAreaメソッドでは、この公式を元に面積を計算して返していますわ

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

クラスを継承しましょう

\footnotesize \textcolor{lime}{ずんだもん:} 無事に他のクラスをメンバ変数として取り込んでクラスの機能の拡張ができたのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、ただC++言語では、このようなクラスの機能の拡張方法以外に、 継承 という機能拡張の方法が用意されていますわ

\footnotesize \textcolor{lime}{ずんだもん:} 継承

\footnotesize \textcolor{pink}{四国めたん:} 継承 を行う場合のクラスの定義については、このようになりますわ

class クラス名: アクセス指定子 基底クラス名 {
  メンバー;
   :
   :
}

\footnotesize \textcolor{lime}{ずんだもん:} 通常のクラスの定義に追加して、クラス名の後にコロン":"を挟んで アクセス指定子基底クラス名 というものが追加されているのだ

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

\footnotesize \textcolor{lime}{ずんだもん:} アクセス指定子 には何を指定すればいいのだ?

\footnotesize \textcolor{pink}{四国めたん:} アクセス指定子 は余程の明確な意図がない限りpublicとしますわ

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

\footnotesize \textcolor{pink}{四国めたん:} ちなみに省略するとprivateを指定したことになるので注意が必要ですわ

\footnotesize \textcolor{lime}{ずんだもん:} 気をつけるのだ

基底クラスってなんなのさ

\footnotesize \textcolor{lime}{ずんだもん:} ところで 基底クラス とは何なのだ?

\footnotesize \textcolor{pink}{四国めたん:} 基底クラス親クラスsuper class とも呼ばれ、新たに定義するクラスの元となるクラスですわ

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

\footnotesize \textcolor{pink}{四国めたん:} ちなみに 基底クラス を元に新たに定義されたクラスは 派生クラス もしくは 子クラスsubclass と呼ばれますわ

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

\footnotesize \textcolor{pink}{四国めたん:} 派生クラスは 明示的に宣言、定義したメンバー、例えばメンバ変数やメソッドの他に 基底クラス で宣言、定義されているメンバーも暗黙的に宣言、定義したことになりますわ

\footnotesize \textcolor{lime}{ずんだもん:} つまり新たに定義したクラスでは、明示的に宣言、定義したメンバーと同様に 基底クラス のメンバーにもアクセスできるようになると云うことか

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

とりあえず実例を見てみましょう

\footnotesize \textcolor{pink}{四国めたん:} とりあえずプログラム例を 継承 を使って書き直してみましょう

circle.h
#pragma once

#ifndef CIRCLE_H
#define CIRCLE_H

#include <iostream>

const double kPI = 3.14159265358979323846;

/// @brief 円
class Circle {
  double diameter_;  // 直径

 public:
  Circle(double diameter) : diameter_(diameter) {}
  ~Circle() {}

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

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

#endif  // CIRCLE_H
ellipse.h
#pragma once

#ifndef ELLIPSE_H
#define ELLIPSE_H

#include "circle.h"

/// @brief 楕円
class Ellipse : public Circle {
  double long_diameter_;  // 長径

 public:
  Ellipse(double short_diameter, double long_diameter)
      : Circle(short_diameter), long_diameter_(long_diameter) {}
  ~Ellipse() {}

  double LongDiameter() { return long_diameter_; }
  void LongDiameter(double long_diameter) { long_diameter_ = long_diameter; }

  double Area() {
    double short_radius = Diameter() / 2.0;
    double long_radius = LongDiameter() / 2.0;
    double a = short_radius * long_radius * kPI;
    return a;
  }
};

#endif  // ELLIPSE_H
hello_world.cpp
#include <iostream>

#include "ellipse.h"

int main(int argc, char* argv[]) {
  Ellipse* pe = new Ellipse(10.0, 15.0);
  std::cout << "楕円の長径は" << pe->LongDiameter() << "cmです。" << std::endl;
  std::cout << "楕円の短径は" << pe->Diameter() << "cmです。" << std::endl;
  std::cout << "楕円の面積は" << pe->Area() << "cm^2です" << std::endl;
  delete pe;
  pe = nullptr;
  return 0;
}

継承の楕円の結果

\footnotesize \textcolor{lime}{ずんだもん:} Circleクラスのインスタンスをメンバ変数とした場合と同じになったのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、プログラム例ではCircleクラスを 基底クラス としたクラスの定義ですわ

\footnotesize \textcolor{lime}{ずんだもん:} つまりEllipseクラスはCircleクラスをpublicとして継承しているのだ

\footnotesize \textcolor{pink}{四国めたん:} なお、楕円の短径として直接CircleのメソッドDiameterをコールできるようになりましたので、EllipseクラスのShortDiameterメソッドは必要なくなりましたわ

\footnotesize \textcolor{lime}{ずんだもん:} ほんとうなのだ

\footnotesize \textcolor{pink}{四国めたん:} さらにメイン関数内からはCircleクラスのDiameterを直接呼び出していますわ

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

基底クラスの初期化は初期化子リストで

\footnotesize \textcolor{lime}{ずんだもん:} ところでEllipseのコンストラクタを見ると、初期化子リストでCircleのコンストラクタを呼び出しているのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、通常、クラスのコンストラクタは、明示的に指定しない場合にはデフォルトのコンストラクタが呼ばれますわ

\footnotesize \textcolor{pink}{四国めたん:} これは 基底クラス であっても例外ではありません

\footnotesize \textcolor{lime}{ずんだもん:} だから初期化子リストでCircleのコンストラクタを呼び出しているのか?

\footnotesize \textcolor{pink}{四国めたん:} はい、Circleクラスには引数のないデフォルトのコンストラクタは定義していませんので、コンストラクタを明示的に指定する必要があるのですわ

\footnotesize \textcolor{lime}{ずんだもん:} コンストラクタを明示的に指定しない場合はどうなるのだ?

\footnotesize \textcolor{pink}{四国めたん:} エラーとなりますわね

Areaが被っていますよ?

\footnotesize \textcolor{lime}{ずんだもん:} ところでメイン関数を見てみるとEllipseのインスタンスを通してAreaメソッドを呼び出しているのだが...

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

\footnotesize \textcolor{lime}{ずんだもん:} でも基底クラスのCircleにもAreaメソッドが存在しているのだが...

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

\footnotesize \textcolor{lime}{ずんだもん:} 名前も引数も同じメソッドが許されるのか

\footnotesize \textcolor{pink}{四国めたん:} いい質問ですわね

\footnotesize \textcolor{pink}{四国めたん:} その点については、次回にお話ししますわね

\footnotesize \textcolor{lime}{ずんだもん:} ぶ~ぶ~

まとめ

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

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

\footnotesize \textcolor{pink}{四国めたん:} 以上で 継承 についてのお話を終わりますわ

Discussion