Zenn
🖥️

【C++言語入門】 第19回 修飾子-続き

2025/03/18に公開

https://youtu.be/_d55T-NNyGc

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

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

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

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

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} 今回は 修飾子 の続きをお話ししますわ

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} constnoexcept以外の 修飾子 か?

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} はい、overridefinalとかのキーワードですわね

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} 待っていたのだ

override

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} まずはoverrideについてお話ししますわね

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} override

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} はい、overrideは、メソッドをオーバーライドしていることを示す 修飾子 ですわ

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

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} 使い方は、派生クラスでオーバーライドしたメソッドの宣言や定義の後にoverrideを付加しますわ

virtual 型 メソッド名(引数...) override { 処理 };

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

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} なお、ソースファイルに定義を行う場合には、ヘッダーファイル側の宣言にのみoverrideを付加しますわ

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} 両方にoverrideを付加するとどうなるのだ?

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

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} そのあたりはnoexceptと異なるのだ

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} ちなみにconstnoexceptと併用する場合には、const noexcept overrideの順番で記述しますわ

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} 順番を変えてもOKなのか?

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} いいえ、順番を変えることはできませんわ

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} ところでオーバーライドするメソッドは、何でもOKなのか?

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} いいえ、オーバーライドするメソッドは、必ず 仮想メソッド でなければなりませんわ

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} つまり基底クラスのメソッドはvirtualが付加されていなければならないと云うことか...

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

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} とりあえず、以前に 抽象クラス についてお話しした際に使用したサンプルプログラムにoverrideを付加してみましょう

figure.h
#pragma once

#ifndef FIGURE_H
#define FIGURE_H

class Figure {
  int border_width_;
  unsigned int color_;

 public:
  Figure(int border_width, unsigned int color)
      : border_width_(border_width), color_(color) {}
  virtual ~Figure() {}

  int BorderWidth() { return border_width_; }
  void BorderWidth(int border_width) { border_width_ = border_width; }
  unsigned int Color() { return color_; }
  void Color(unsigned int color) { color_ = color; }

  virtual double Area() const noexcept = 0;
};

#endif  // FIGURE_H
circle.h
#pragma once

#ifndef CIRCLE_H
#define CIRCLE_H

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

const double kPI = 3.14159265358979323846;

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

 public:
  Circle(double diameter) : Figure(1, 0xFF000000), diameter_(diameter) {}
  virtual ~Circle() {};

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

  virtual double Area() const noexcept override;
};

#endif  // CIRCLE_H
circle.cpp
double Circle::Area() const noexcept {
  double radius = Diameter() / 2.0;
  double a = radius * radius * kPI;
  return a;
}
hello_world.cpp
#include <iostream>
#include "circle.h"

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

オーバーライド

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

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} はい、まずFigureクラスのAreaメソッドは 純粋仮想関数 として宣言していますわ

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} constおよびnoexceptの後に= 0が付加されているのだ

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} そしてCircleクラスでAreaメソッドをオーバーライドしていますわ

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} メソッドの宣言にoverrideが付加されているのだ

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} ソースファイルのAreaメソッドの定義についてはoverrideを付加していないことに注意ですわ

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} 付加するとエラーになると言っていたのだ

overrideの目的

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} ところでoverrideを付加することで、何かメリットがあるのか?

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} overridenoexceptと同様に、メソッドの動作に影響を与える 修飾子 ではありませんわ

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} ですので、付加しなくてもメソッドは正常に動作しますわね

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} では、何でoverrideを付加するのだ?

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} ひとつは、メソッドがオーバーライドされた 仮想メソッド であることを明示できることですわ

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} 明示したからと云ってメリットが感じられないのだ

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} 確かに簡単なプログラムでは余りメリットに感じませんが、通常のメソッドと 仮想メソッド を複数抱えているクラスを扱う際には意外に便利なのですわ

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

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} もうひとつは、オーバーライドしたメソッドが 仮想メソッド であることを保証できることですわ

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

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} 基底クラスのメソッドにvirtualが付加されていない場合には、コンパイラがエラー、もしくは警告を出力してくれますわ

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} 間違えて 仮想メソッド 以外をオーバーライドしてしまうことを防げると云うことか

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

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} さらに、基底クラスに存在しないメソッドをオーバーライド?することも防げますわ

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} そんなことがあるのか?

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} これは結構ある間違いで、基底クラスの 仮想メソッド のメソッド名を間違えてしまった場合に、よく発生しますわね

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} 例えばFigureクラスのAreaメソッドをCircleクラスでAresメソッドとしてオーバーライド?してしまうとか...

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

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} その場合でもコンパイラがエラー、もしくは警告を出してくれるので、直ぐに修正が可能ですわ

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} なるほど、なんとなく便利なのはわかったのだ

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} そう云う訳でoverrideについては、積極的に使うようにしましょう

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

final

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} 次はfinalですわね

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} final

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} はい、finalは、派生クラスでメソッドをオーバーライドすることを禁止しますわ

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

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} 使い方は、基底クラスでオーバーライド可能な 仮想メソッド の宣言や定義の後にfinalを付加しますわ

virtual 型 メソッド名(引数...) final { 処理 };

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

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} なお、ソースファイルに定義を行う場合には、ヘッダーファイル側の宣言にのみfinalを付加しますわ

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} 両方にfinalを付加するとどうなるのだ?

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

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} そのあたりはoverrideと同じなのだ

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} ちなみにconstnoexceptと併用する場合には、const noexcept finalの順番で記述しますわ

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} 順番を変えてもOKなのか?

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} いいえ、順番を変えることはできませんわ

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} overridefinalについては、どうなのだ?

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} どちらを先に記述しても問題ありませんわ

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

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} とりあえず、サンプルプログラムにfinalを付加してみましょう

circle.h
#pragma once

#ifndef CIRCLE_H
#define CIRCLE_H

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

const double kPI = 3.14159265358979323846;

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

 public:
  Circle(double diameter) : Figure(1, 0xFF000000), diameter_(diameter) {}
  virtual ~Circle() {};

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

  virtual double Area() const noexcept override final;
};

#endif  // CIRCLE_H

ファイナル

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} いちおう、CircleクラスのAreaメソッドにfinalを付加してみました。

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} とくに変わりはないのだ

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} はい、Circleクラスを継承しているクラスがありませんので、finalの効果はありませんわね

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} 効果が目に見えるようにしてほしいのだ

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} わかりましたわ

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} それではFigureクラスのAreaメソッドにfinalを付加してみますわ

figure.h
#pragma once

#ifndef FIGURE_H
#define FIGURE_H

class Figure {
  int border_width_;
  unsigned int color_;

 public:
  Figure(int border_width, unsigned int color)
      : border_width_(border_width), color_(color) {}
  virtual ~Figure() {}

  int BorderWidth() { return border_width_; }
  void BorderWidth(int border_width) { border_width_ = border_width; }
  unsigned int Color() { return color_; }
  void Color(unsigned int color) { color_ = color; }

  virtual double Area() const noexcept final { return 0.0; }
};

#endif  // FIGURE_H
circle.h
#pragma once

#ifndef CIRCLE_H
#define CIRCLE_H

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

const double kPI = 3.14159265358979323846;

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

 public:
  Circle(double diameter) : Figure(1, 0xFF000000), diameter_(diameter) {}
  ~Circle() {};

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

  virtual double Area() const noexcept override;
};

#endif  // CIRCLE_H

ファイナルエラー

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

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} まず、 純粋仮想関数 ではfinalを付加する意味がありませんので、0.0を返すメソッドに変更していますわ

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} 純粋仮想関数 はオーバーライドを要求するのに、finalでオーバーライドを禁止するのは意味不明なのだ

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} 結果としてCircleクラスのAreaメソッドでオーバーライドした部分でエラーが発生していますわ

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} CircleクラスのAreaメソッドのfinalは消しているのだ

クラスにfinal

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} ところで、finalはメソッドだけではなく、クラスに対しても付加することが可能ですわ

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

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} 使い方はクラス名の後にfinalを付加しますわ

class クラス名 final : public 基底クラス名 { クラス定義 };

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

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} クラスにfinalを付加すると、クラスの継承自体が禁止されますわ

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} つまり、派生クラスを作ることができないということなのだ

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} はい、とりあえずFigureクラスにfinalを付加してみましょう

figure.h
#pragma once

#ifndef FIGURE_H
#define FIGURE_H

class Figure final {
  int border_width_;
  unsigned int color_;

 public:
  Figure(int border_width, unsigned int color)
      : border_width_(border_width), color_(color) {}
  virtual ~Figure() {}

  int BorderWidth() { return border_width_; }
  void BorderWidth(int border_width) { border_width_ = border_width; }
  unsigned int Color() { return color_; }
  void Color(unsigned int color) { color_ = color; }

  virtual double Area() const noexcept { return 0.0; }
};

#endif  // FIGURE_H

クラスのファイナルエラー

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

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} "Figure"の後にfinalを付加し、Areaメソッドからfinalを削除していますわ

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

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} 結果としてCircleクラスの継承部分がエラーとなっていますわ

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

finalの目的

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} ところで、finalを付加するメリットがあるのか?

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} finaloverridenoexceptと同様に、メソッドの動作に影響を与える 修飾子 ではありませんわ

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} ですので、付加しなくてもメソッドやクラスは正常に動作しますわね

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

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} むしろ、クラスを継承したり、継承クラスでオーバーライドができなくなるので、邪魔に感じることも多いと思いますわ

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} では、何でfinalを付加するのだ?

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} 主なメリットは、クラスの設計思想を逸脱して派生クラスを作成して欲しくないことを明示できることですわ

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} クラスの設計思想?

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} はい、クラスの詳細を理解せずに、ほかのひとが派生クラスを作成したり、メソッドをオーバーライドすると、バグの原因になることが多いのですわ

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

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} ところで、finalは基本的にヘッダーファイルにのみ記述されますわ

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} ソースファイル側で記述するとエラーとなるのだ

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} ですので、無理やりオーバーライドしたい場合には、finalを削除してしまえばオーバーライドできてしまいますわ

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

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} つまり、基底クラスの作者の意図を明示することしかできないのですわ

ずんだもん:\footnotesize \textcolor{lime}{ずんだもん:} それって意味あるのか?

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} 元々、C++言語はクラスを継承してメソッドをオーバーライドする拡張性、つまり ポリモーフィズム が重要な要素ですわ

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

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} それを意図的に殺してしまうfinalは、余程の明確な意図がある場合以外は、使用を控えるべきですわ

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

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} 逆に、finalを付加されたクラスやメソッドは、作者が絶対に継承やオーバーライドして欲しくないと考えていることを理解すべきですわね

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

まとめ

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

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

四国めたん:\footnotesize \textcolor{pink}{四国めたん:} 以上で 修飾子-続き を終わりにしますわ

Discussion

ログインするとコメントできます