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
キーワードを使用しますわ
\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