🖥️

【C++言語入門】 第15回 例外-続き

に公開

https://youtu.be/cLOb3AMmImE

四国めたん
\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}{四国めたん:} とりあえずCircleクラスにTestメソッドを追加し、その中から 例外 を投げてみますわ

circle.h
#pragma once

#ifndef CIRCLE_H
#define CIRCLE_H

#include <iostream>
#include <stdexcept>

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; }

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

  virtual void Test() {
    std::cout << "例外を投げます" << std::endl;
    throw std::exception("クラスのメソッドからの例外です");
    return;
  }
};

#endif  // CIRCLE_H
hello_world.cpp
#include <iostream>
#include <stdexcept>

#include "circle.h"

int main(int argc, char* argv[]) {
  try {
    Circle c(10.0);
    c.Test();
  } catch (const std::exception& e) {
    std::cout << e.what() << std::endl;
  }
  return 0;
}

メソッドで例外

\footnotesize \textcolor{pink}{四国めたん:} Testメソッドから投げられた 例外 をメイン関数内のcatchでしっかりと捕捉していますわ

\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}{四国めたん:} とりあえずCircleクラスのコンストラクタから 例外 を投げてみますわ

circle.h
#pragma once

#ifndef CIRCLE_H
#define CIRCLE_H

#include <iostream>
#include <stdexcept>

const double kPI = 3.14159265358979323846;

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

 public:
  Circle(double diameter) : diameter_(diameter) {
    std::cout << "例外を投げます" << std::endl;
    throw std::exception("コンストラクタからの例外です");
  }
  ~Circle() {};

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

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

#endif  // CIRCLE_H
hello_world.cpp
#include "circle.h"

int main(int argc, char* argv[]) {
  try {
    Circle c(10.0);
  } catch (const std::exception& e) {
    std::cout << e.what() << std::endl;
  }
  return 0;
}

コンストラクタでの例外

\footnotesize \textcolor{lime}{ずんだもん:} メイン関数内ではCircleクラスのインスタンスを生成しているだけなのだ

\footnotesize \textcolor{pink}{四国めたん:} ですが、コンストラクタから 例外 が投げられるのでcatchで捕捉されていますわ

デストラクタでは例外を投げられません

\footnotesize \textcolor{lime}{ずんだもん:} コンストラクタで 例外 を投げられるなら、デストラクタでも 例外 を投げていいのか?

\footnotesize \textcolor{pink}{四国めたん:} いいえ、デストラクタについては 例外 を投げることを推奨されていませんわ

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

\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}{四国めたん:} とりあえずCircleクラスのデストラクタから 例外 を投げてみますわ

circle.h
#pragma once

#ifndef CIRCLE_H
#define CIRCLE_H

#include <iostream>
#include <stdexcept>

const double kPI = 3.14159265358979323846;

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

 public:
  Circle(double diameter) : diameter_(diameter) {
  }
  ~Circle() {
    std::cout << "例外を投げます" << std::endl;
    throw std::exception("デストラクタからの例外です");
  };

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

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

#endif  // CIRCLE_H
hello_world.cpp
#include "circle.h"

int main(int argc, char* argv[]) {
  try {
    Circle c(10.0);
  } catch (const std::exception& e) {
    std::cout << e.what() << std::endl;
  }
  return 0;
}

デバッグエラー

\footnotesize \textcolor{pink}{四国めたん:} 実行するとデバッグエラーが発生していますわね

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

\footnotesize \textcolor{pink}{四国めたん:} 警告も出ていますわね

警告

\footnotesize \textcolor{lime}{ずんだもん:} 「'Circle::~Circle': 例外をスローしないはずだがそれをする関数。」という警告が出ているのだ

\footnotesize \textcolor{pink}{四国めたん:} ということで、デストラクタから 例外 を投げてはいけませんわ

\footnotesize \textcolor{lime}{ずんだもん:} でも、デストラクタ内でエラーや 例外 が発生した場合にはどうするのだ?

\footnotesize \textcolor{pink}{四国めたん:} もし、デストラクタ内でエラーや 例外 が発生した場合には、デストラクタ内でエラー処理やcatchによる捕捉をおこなって、処理を完結しますわ

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

\footnotesize \textcolor{pink}{四国めたん:} なお、デストラクタ内で発生する 例外 は、経験上、致命的なものは少ないですわ

\footnotesize \textcolor{pink}{四国めたん:} ですので、catchで捕捉するだけで、そのまま何もしないというのも選択肢ですわね

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

例外を再スローします

\footnotesize \textcolor{lime}{ずんだもん:} ところで 例外catchで捕捉した後、状況によって受け取った 例外 を再度投げることはできるのか?

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

\footnotesize \textcolor{pink}{四国めたん:} 例を見てみましょう

#include <iostream>
#include <stdexcept>

void Test() {
  try {
    std::cout << "例外を投げます" << std::endl;
    throw std::exception("例外が投げられました");
  } catch (const std::exception&) {
    std::cout << "例外を捕捉しました" << std::endl;
    throw;
  }
}

int main(int argc, char* argv[]) {
  try {
    Test();
  } catch (const std::exception& e) {
    std::cout << "メイン関数で例外を捕捉しました" << std::endl;
    std::cout << e.what() << std::endl;
  }
  return 0;
}

再スロー

\footnotesize \textcolor{pink}{四国めたん:} Test関数内で投げられた 例外 は、Test関数内でcatchにより捕捉されますわ

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

\footnotesize \textcolor{pink}{四国めたん:} そしてthrow;により捕捉された 例外 が再度投げられていますわ

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

\footnotesize \textcolor{pink}{四国めたん:} なお、Test関数内のcatchでは、処理に 例外オブジェクト を使用していませんので、オブジェクト名を省略していますわ

\footnotesize \textcolor{lime}{ずんだもん:} わかったのだ

\footnotesize \textcolor{pink}{四国めたん:} また、捕捉した 例外 をそのまま投げる場合には、throwに対象のオブジェクトを指定する必要はありませんわ

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

例外処理から別のオブジェクトを投げる

\footnotesize \textcolor{pink}{四国めたん:} catchの中から、捕捉した 例外 を投げることはできましたわ

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

\footnotesize \textcolor{pink}{四国めたん:} それ以外にも、別のオブジェクトを作成して 例外 として投げることもできますわ

#include <iostream>
#include <stdexcept>

void Test() {
  try {
    std::cout << "例外を投げます" << std::endl;
    throw std::exception("例外が投げられました");
  } catch (const std::exception&) {
    std::cout << "例外を捕捉しました" << std::endl;
    throw "文字列のオブジェクト";
  }
}

int main(int argc, char* argv[]) {
  try {
    Test();
  } catch (const std::exception& e) {
    std::cout << "メイン関数で例外を捕捉しました" << std::endl;
    std::cout << e.what() << std::endl;
  } catch (const char str[]) {
    std::cout << str << std::endl;
  }
  return 0;
}

文字列のオブジェクト

\footnotesize \textcolor{pink}{四国めたん:} 今回は、Test関数内のcatch例外 を捕捉した後、文字列を 例外オブジェクト として投げていますわ

\footnotesize \textcolor{lime}{ずんだもん:} 例外オブジェクト として文字列も使えるのか?

\footnotesize \textcolor{pink}{四国めたん:} 使えますわね

\footnotesize \textcolor{pink}{四国めたん:} そして、メイン関数内ではexceptionクラスのインスタンス以外に文字列オブジェクトをcatchするように追加していますわ

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

\footnotesize \textcolor{pink}{四国めたん:} Test関数からは文字列の 例外オブジェクト が投げられるので、文字列の 例外オブジェクト を捕捉するcatchの部分が処理されますわ

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

まとめ

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

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

\footnotesize \textcolor{pink}{四国めたん:} 以上で 例外-続き を終了しますわ

Discussion