C++ 再入門 その37 - 例外を捕まえる
前回、C++の例外の基本として try で括った範囲で例外が発生すると対応する catch に飛ぶんだよというところまで説明しました。
C++ 再入門 その36 - 例外処理の基本
自分で例外を発生させるコードを書かなくても、既に標準ライブラリは例外を使って書かれています。ですから例外をキャッチしないと、そこでプログラムは(標準の例外処理によって)停止してしまうことになります。ですからいきなり停止しては困るのであれば、ちゃんと例外を受け止めてあげなければなりません。
標準ライブラリで使われる例外は stdexcept というヘッダで定義されています。まず except という基底クラスがあり、ここからいくつかの派生クラスが導出されています。
exception クラス
C++/標準ライブラリ/stdexcept
基底クラスで使えるメンバ関数はあまり多くないのですが、メッセージを表示、記録するのに what()がよく使われます。派生クラスとして logic_error と runtime_error があり、それぞれがさらに具体的なエラーの種類に応じてさらに継承されています。
stdexcept
一般的には、こんな感じで使います。
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
int main() {
string s = "*";
try {
int n = stoi(s);
cout << n << endl;
}
catch (exception e) {
cout << "exception:" << e.what() << endl;
}
}事前に充分にチェックを行えば例外を発生させないようにすることもできるかもしれませんが、文字通り例外的なケースに対して、どこまで事前にチェックするかはそのコストを考えると悩ましいことがほとんどです。それでも例外は発生してしまうことは考えられるのですが、どう対処すべきかはケースバイケースです。いずれにせよプログラムが停止する前に使用中のリソースを可能な範囲で解放し、例外が発生したことをエラー出力なりログなどに書き込んでから終了したほうが親切です。
もっとも例外をキャッチすれば完璧かというと、必ずしもそうであるとは限らず。例えば整数の0除算は exception や … を使ってもうまく拾えません。
#include <iostream>
#include <stdexcept>
using namespace std;
int main() {
int i = 0;
try {
cout << 3 / i << endl;
}
catch (exception e) {
cout << "exception:" << e.what() << endl;
}
catch (...) {
cout << "unnknown except!" << endl;
}
}CppSample.exe (プロセス 10124) は、コード -1073741676 (0xc0000094) で終了しました。
main関数であるとか、処理の上位にあたる関数であれば、例外を拾っても具体的な原因を追うには情報が無くて対処がわからないことが普通です。ですが関数内の特定の処理であれば、何らかの対処を行って再実行したり、諦めるのであれば、その関数で利用しているリソースを解放してから例外をthrowして呼び出した関数に処理を委ねる書き方をします。
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
void f(string s) {
try {
cout << stoi(s) << endl;
}
catch (exception e) {
cout << "exception in f():" << e.what() << endl;
throw e;
}
}
int main() {
try {
f("*");
}
catch (exception e) {
cout << "exception in main():" << e.what() << endl;
}
}exception in f():invalid stoi argument
exception in main():invalid stoi argument
上位関数で例外を受け取るのはプログラムが停止される前に何らかの処理を行うためで、それ以外では例外を受け取るのは明確な対処ができる場合やリソースがリークしないようにするためなどに限定すべきです。単に受け取った例外を上位に伝えるためだけにcatchするのは意味がありませんし(何も書かなくてもそうなる)、例外の発生場所を追うためであれば、昔は使えなかった stacktrace なんてものも使えます。
いずれにせよ try ブロックの中で例外が発生すれば、そのブロックを抜けて catch ブロックに処理が移るので、try ブロックのローカルなリソースは解放されますが、必ずしもデストラクタで必要なリソース(参照で使われているものなど)が解放されるようになっているとは限らないので、catch の中でそれを確認してちゃんと解放しないとなりません。例外は慣れないとその使い所が難しいのですが、不用意にプログラムが停止しないように、そして処理を継続する場合には使いかけのリソースのお片付けを忘れないことが肝要です。
まあ自分で例外を設計しなくてもまずはライブラリが投げてくる例外に対処できるようにはなりましょう。実はライブラリではなくて言語仕様で決まっている例外もこれとは別にあるのですよね。
ヘッダ画像は、以下のものを使わせていただきました。https://commons.wikimedia.org/wiki/File:ISO_C%2B%2B_Logo.svg
Jeremy Kratz - https://github.com/isocpp/logos , パブリック・ドメイン,
https://commons.wikimedia.org/w/index.php?curid=62851110による
#プログラミング #プログラミング言語 #プログラミング講座 #CPP #例外処理 #exception #stdexcept #logical_error #runtime_error
いいなと思ったら応援しよう!
頂いたチップは記事を書くための資料を揃えるために使わせていただきます!