🖥️

【C++言語入門】 第14回 例外

2025/03/11に公開

https://youtu.be/2HDdC4HLzJk

四国めたん
\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}{四国めたん:} C言語も含めて、関数内でエラーが発生した場合にはどうしますか?

  • エラーコードを戻り値として返す
  • エラー処理専用の関数を呼び出す
  • プログラムを直ちに中断する
  • スルーする

\footnotesize \textcolor{lime}{ずんだもん:} 基本的にはエラーコードを返すのだ

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

\footnotesize \textcolor{pink}{四国めたん:} その他に「エラー処理用の関数を呼び出す」、「中断する」そして「スルーする」などが考えられますわ

\footnotesize \textcolor{lime}{ずんだもん:} いや、「スルーする」のは悪手なのだ

\footnotesize \textcolor{pink}{四国めたん:} たしかに、余程の特殊な場合を除き、止めた方がいいですわね

\footnotesize \textcolor{pink}{四国めたん:} そして「中断する」のは、 0による除算 などの予期しないエラーの場合に発生する場合がありますわ

\footnotesize \textcolor{lime}{ずんだもん:} そうは言っても、ユーザーとしては受け入れがたいのではないか?

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

\footnotesize \textcolor{pink}{四国めたん:} そして「エラー処理用の関数を呼び出す」のは、それなりに穏当な対処方法と思いますわ

\footnotesize \textcolor{lime}{ずんだもん:} でもエラー処理用の関数内だけで対処ができるかは不透明なのだ

\footnotesize \textcolor{pink}{四国めたん:} かなり難しいと思いますわ

\footnotesize \textcolor{lime}{ずんだもん:} 結局「エラーコードを戻り値として返す」のが一番なのだ

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

C++言語には例外がありますよ

\footnotesize \textcolor{pink}{四国めたん:} C++言語には新たにエラーの対処方法として、 例外 もしくは Exception と云う仕組みが導入されましたわ

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

\footnotesize \textcolor{pink}{四国めたん:} はい、関数内でエラーが発生した際に、関数の戻り値とは別に、エラーを表す 例外 オブジェクトを返す仕組みですわ

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

throwで例外を投げます

\footnotesize \textcolor{lime}{ずんだもん:} ところで 例外 オブジェクトはどのように返すのだ?

\footnotesize \textcolor{pink}{四国めたん:} 関数内で 例外 オブジェクトを返すにはthrowキーワードを使用しますわ

throw オブジェクト;

\footnotesize \textcolor{lime}{ずんだもん:} オブジェクトはどのようなものを使うのだ?

\footnotesize \textcolor{pink}{四国めたん:} "throw"するオブジェクトについては、整数などを使ったエラーコードでも、独自のクラスのインスタンスでもかまいませんわ

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

\footnotesize \textcolor{pink}{四国めたん:} ただ、C++言語の標準テンプレートライブラリには専用のexceptionクラスが準備されていますわ

\footnotesize \textcolor{lime}{ずんだもん:} それなら、通常はそれらを使えばいいのだ

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

exceptionクラスとその派生クラス

\footnotesize \textcolor{lime}{ずんだもん:} ところでexceptionについて教えて欲しいのだ

\footnotesize \textcolor{pink}{四国めたん:} exceptionクラスと、その派生クラスは 例外 を表すための専用のクラスですわ

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

\footnotesize \textcolor{pink}{四国めたん:} throwを使って投げる 例外 オブジェクトは、exceptionクラスか、そこから派生したクラスのインスタンスに限定することで、扱いが容易になりますわ

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

\footnotesize \textcolor{pink}{四国めたん:} なお、自身でexceptionクラスを継承した派生クラスを作成してもかまいませんわ

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

\footnotesize \textcolor{pink}{四国めたん:} なお、標準テンプレートライブラリでは"stdexception"ヘッダーに以下のような 例外 クラスを定義していますわ

  • exception: 例外 全体の基底クラス
    • logic_error: 論理エラーのクラス
      • domain_error: 定義域エラーのクラス
      • invalid_argument: 無効な引数を表すクラス
      • length_error: 指定の長さが対応不可を表すクラス
      • out_of_range: 引数が範囲外であることを示すクラス
    • runtime_error: 実行時エラーのクラス
      • range_error: 計算時に値が範囲外になったことを示すクラス
      • overflow_error: 計算結果がオーバーフローしたことを示すクラス
      • underflow_error: 計算結果がアンダーフローしたことを示すクラス

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

\footnotesize \textcolor{pink}{四国めたん:} これらのクラスはインスタンス生成時に引数として文字列を指定できますわ

#include <stdexception>
    :
    :
throw std::exception("error test");
    :
    :

\footnotesize \textcolor{pink}{四国めたん:} ちなみにexceptionクラスとその派生クラスは、std名前空間に属していますので、std::の指定が必要ですわ

\footnotesize \textcolor{lime}{ずんだもん:} 覚えておくのだ

\footnotesize \textcolor{pink}{四国めたん:} また、文字列にはどのようなエラーかを具体的に記述しますわ

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

例外をキャッチしましょう

\footnotesize \textcolor{lime}{ずんだもん:} 投げられた 例外 はどのようにして使うのだ?

\footnotesize \textcolor{pink}{四国めたん:} 投げられた 例外 を処理するために用意されたのがtry~catch構文ですわ

try {
  処理
} catch (const 型& オブジェクト名) {
  例外処理
}

\footnotesize \textcolor{pink}{四国めたん:} tryの処理中に型で示された 例外 のオブジェクトが投げられると、catch中の例外処理が行われますわ

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

\footnotesize \textcolor{pink}{四国めたん:} また、投げられた 例外 オブジェクトは、オブジェクト名によりアクセスすることができますわ

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

\footnotesize \textcolor{pink}{四国めたん:} なお、例外処理中でオブジェクトを使用しない場合には、オブジェクト名を省略することも可能ですわ

\footnotesize \textcolor{lime}{ずんだもん:} それは便利なのだ

\footnotesize \textcolor{pink}{四国めたん:} ちなみに、型の指定はconst参照 とすることを推奨しますわ

\footnotesize \textcolor{lime}{ずんだもん:} 型 オブジェクト名のように、通常の指定方法ではダメなのか?

\footnotesize \textcolor{pink}{四国めたん:} 通常の指定方法でも問題はありませんわ

\footnotesize \textcolor{pink}{四国めたん:} ただ、例外処理中に 例外 オブジェクトの内容を変更することは、ほぼありませんのでconstでOKですわ

\footnotesize \textcolor{pink}{四国めたん:} また、 参照 でない場合には 例外 オブジェクトがコピーされますので、無駄が発生しますわ

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

関数を抜けても例外オブジェクトは存在?

ここで疑問を持った方も多いかと思います

通常、関数を抜けると、関数内で宣言、生成したインスタンスは破壊されてアクセスできなくなります

普通に考えるとcatch例外 オブジェクトを参照で受けた場合、関数からは抜けているのでアクセスできなくなっているのではなのでしょうか?

その場合、new例外 オブジェクトを生成して、catchではポインタで受けるのが正解なのではと思います

まぁ、そういう方法もあるのですが、生成されたオブジェクトをどこで破棄するのかなどの面倒な処理が追加されます

一応、そういったことは考慮されているようで、 例外 として投げられたオブジェクトは関数を抜けた後でも使えるように特例が設けられているようです

ですので 例外オブジェクト は安心して 参照 で受けましょう

例外を使ったプログラム

\footnotesize \textcolor{pink}{四国めたん:} 例外の使い方にはいろいろとバリエーションがありますわ

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

\footnotesize \textcolor{pink}{四国めたん:} ですので具体的なプログラムを見ながら説明していきますわ

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

同じ関数内で例外処理

\footnotesize \textcolor{pink}{四国めたん:} まずは 例外 を投げた関数内で 例外 の処理を行う場合ですわ

#include <iostream>
#include <stdexcept>

int main(int argc, char* argv[]) {
  try {
    std::cout << "例外を投げます" << std::endl;
    throw std::exception("テストのための例外です");
    // これ以降の処理はスキップされる
  } catch (const std::exception& e) {
    std::cout << e.what() << std::endl;
  }
  return 0;
}

同じ関数内で例外

\footnotesize \textcolor{lime}{ずんだもん:} tryの処理中で直接throwにより 例外 を投げているのだ

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

\footnotesize \textcolor{lime}{ずんだもん:} exceptionクラスのwhatメソッドは、何をするのだ?

\footnotesize \textcolor{pink}{四国めたん:} 生成時に指定した文字列を返しますわ

\footnotesize \textcolor{lime}{ずんだもん:} たしかに「テストのための例外です」と返っているのだ

\footnotesize \textcolor{pink}{四国めたん:} なおthrowにより 例外 が投げられると、それ以降の処理はスキップされてcatch内の 例外処理 が実行されますわ

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

関数内で例外、呼び出し側で例外処理

\footnotesize \textcolor{pink}{四国めたん:} 次はtryの処理中で呼び出された関数で投げられた 例外catchする例ですわ

#include <iostream>
#include <stdexcept>

void Test() {
  std::cout << "例外を投げます" << std::endl;
  throw std::exception("テストのための例外です");
  // これ以降の処理はスキップされる
  return;
}

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

別の関数内で例外

\footnotesize \textcolor{lime}{ずんだもん:} tryの処理中で呼び出したTest関数内で 例外 を投げているのだ

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

\footnotesize \textcolor{pink}{四国めたん:} なおthrowにより 例外 が投げられると、関数内の残りの処理はスキップされて、呼び出し側のcatch内の 例外処理 が実行されますわ

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

より深い関数内で例外

\footnotesize \textcolor{pink}{四国めたん:} 更に深い呼び出しの関数からの 例外 を処理する例も見てみましょう

#include <iostream>
#include <stdexcept>

void Test() {
  std::cout << "例外を投げます" << std::endl;
  throw std::exception("テストのための例外です");
  // これ以降の処理はスキップされる
  return;
}

void Test0() {
  Test();
  std::cout << "この処理はスキップされる" << std::endl;
  return;
}

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

深い関数内で例外

\footnotesize \textcolor{lime}{ずんだもん:} tryの処理中で呼び出したTest0で呼び出したTest関数で 例外 を投げているのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、 例外catchによりオブジェクトをキャッチされるまで呼び出し側をさかのぼっていきますわ

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

\footnotesize \textcolor{pink}{四国めたん:} その間、他の処理は全てスキップされますわ

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

\footnotesize \textcolor{pink}{四国めたん:} この例ではTest0関数内のTestを呼び出した際に 例外 が発生しますわ

\footnotesize \textcolor{pink}{四国めたん:} ですのでTest0関数内での この処理はスキップされる というコンソールへの出力処理はスキップされますわ

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

\footnotesize \textcolor{pink}{四国めたん:} ちなみに、メイン関数内でも 例外 がキャッチされないとプログラムが中断されますわ

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

\footnotesize \textcolor{pink}{四国めたん:} 確認してみましょう

#include <iostream>
#include <stdexcept>

void Test() {
  std::cout << "例外を投げます" << std::endl;
  throw std::exception("テストのための例外です");
  // これ以降の処理はスキップされる
  return;
}

void Test0() {
  Test();
  std::cout << "この処理はスキップされる" << std::endl;
  return;
}

int main(int argc, char* argv[]) {
  Test0();
  return 0;
}

例外による中断

\footnotesize \textcolor{lime}{ずんだもん:} バンドルされない例外 が発生してプログラムが止まったのだ

全ての例外のキャッチ

\footnotesize \textcolor{lime}{ずんだもん:} ところでcatchする 例外 の指定は必要なのか?

\footnotesize \textcolor{pink}{四国めたん:} 全ての例外 を指定することも可能ですわ

#include <iostream>
#include <stdexcept>

void Test(int num) {
  std::cout << "例外を投げます" << std::endl;
  if (num == 0) {
    throw std::exception("テストのための例外です");
  } else {
    throw num;
  }
  // これ以降の処理はスキップされる
  return;
}

int main(int argc, char* argv[]) {
  for (int i = 0; i < 2; i++) {
    try {
      Test(i);
    } catch (const std::exception& e) {
      std::cout << e.what() << std::endl;
    } catch (...) {
      std::cout << "全ての例外を受け付けます。" << std::endl;
    }
  }
  return 0;
}

全ての例外

\footnotesize \textcolor{lime}{ずんだもん:} 今回、Test関数ではint型の引数をそのままthrowしているのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、throwするオブジェクトはexceptionクラス以外でも問題ありませんわ

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

\footnotesize \textcolor{pink}{四国めたん:} 次にメイン関数内では1つのtryに対して複数のcatchを指定していますわ

\footnotesize \textcolor{lime}{ずんだもん:} お~、catchは複数を記述することが可能なのか...

\footnotesize \textcolor{pink}{四国めたん:} はい、投げられた 例外 の型とcatchに指定した型を上から順番に比較していき、合致したcatchの例外処理を実行しますわ

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

\footnotesize \textcolor{pink}{四国めたん:} そして、catchで三点リーダー"..."を指定した場合には、全ての 例外 オブジェクトの型と一致しますわ

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

まとめ

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

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

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

\footnotesize \textcolor{lime}{ずんだもん:} 一旦?

\footnotesize \textcolor{pink}{四国めたん:} はい、次回に 例外 の続きをお話ししますわ

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

Discussion