C++ 再入門 その39 - 例外のためのクラス
C++の例外に関しては、結構思い出す必要があることが多くて、ボチボチのペースです。
C++ 再入門 その38 - 関数に例外を仕込む
基本的な使い方は上記の記事に書きましたが、もう一度おさらいをしておきます。
まず、throw で投げる例外は、その型で振り分けられて catch されるので、どんな型で throw するかが大事です。実は型は何でも良いのです。(BASICのように)番号だけを伝えれば良いのであれば int で構わないですし、以下のように列挙型を使ってもわかりやすいです。
int main() {
enum ErrState { noErr, Invalid, Ignore };
try {
if (true) throw Invalid;
}
catch (ErrState e) {
switch (e) {
case noErr: return 0;
case Invalid: return 1;
case Ignore: return -1;
}
}
return 0;
}※戻り値1でプロセスが終了する
このような使い方は同じ関数内で確実に catch していれば特に問題ありませんが、この関数を呼び出した側で catch させるにはエラー型の定義にアクセスできるようにすることと、確実に catch するようにドキュメント化するなどが必要です。それでもオリジナルの型を catch してくれるかどうかは担保できず、catch (…) で受けることとなってしまい、せっかく throw した情報を受け取ることができなくなります。
そこで最低でもある程度の情報を受け取ってもらうために、関数をまたいで使う例外型は標準ライブラリにある exception から派生させることが一般的です。
#include <iostream>
#include <exception>
#include <stdexcept>
#include <string>
using namespace std;
class CustomException : public exception {
private:
string message_;
public:
CustomException(const string& msg) : message_(msg) {}
const char* what() const noexcept override { return message_.c_str(); }
};
int main() {
try {
if (true) throw CustomException("Custum Exception.");
}
catch (const CustomException& e) {
cerr << e.what() << endl;
}
catch (const exception& e) {
cerr << e.what() << endl;
}
return 0;
}Custum Exception.
これは単にthrow する時にエラーメッセージを設定できるだけのクラスですが、少し説明が必要でしょう。メンバ関数 what() のプロトタイプに override が付いているのは、親クラス(exception)で定義されている関数を再定義していることを示しており、このオマジナイでクラスを親クラスの参照から呼び出しても子クラスである CustomException の関数が呼び出されます。
CustomException を catch している行をコメントアウトしても、exception として catch することで以下のように CustomException で設定したメッセージが what() で出力されます。
Custum Exception.
それから同じ場所にある noexcept ですが、この関数が例外を throw しないことを明示的に宣言するものです(C++11から)。例外処理を行う関数でさらに例外を投げてしまうと catch する方での判断が複雑になりすぎますし、タダでさえ最適化しにくくなる例外処理に関するコードをちょっとだけシンプルなコードを出してくれるようにしてくれます。ただし、これを書くと例外が throw されてるコードがあっても無視される、または停止されるとになるので、呼び出す関数は慎重に選択する必要があります。
C++での例外処理について
このあたりはまだちゃんと説明していなかったような気もするので、いずれ取り上げましょう。
さて、throw で指定された例外処理のためのクラス宣言は、そこでオブジェクトがコンストラクタを呼び出すことで作成され、catch が実体で宣言されていれば、それがコピーされ、参照で宣言されていればポインタが渡されます。そして catch 節の実行が終わると try ブロックを抜ける訳です。つまり catch 節を実行している間は try ブロックを抜けていないので、throw 時点でのスタックは生きているので参照を使っても構わないわけです(むしろ推奨される)。そして catch を抜けた時(try を抜けるとき)に、スタックが巻き戻され必要なデストラクタも実行される手筈になっています。これは関数を跨いでも同じです。throw はある意味で関数を呼び出しているのと同じようになっていると考えても良いでしょう。
今回は例外の大親クラスである exception から派生させましたが、自身の例外が既にある特定の例外の仲間だと考えれば、そこから派生させるほうが catch する時に判断がしやすいです(親クラスとして catch されることがあるのは、既に説明した通り)。例えば runtime_error であるとか invalid_argument などを使うことも多いかと思います。
10.1 例外処理
オリジナルの例外クラスを作成する場合、基本的にはこのように標準ライブラリのクラスから派生させて作ることになると思います。受け取る側では親クラスとして処理することもあるので、親子関係を充分に理解して設計する必要があります。また catch 節は、呼び出し元のものかもしれないのでからアクセスできる変数、オブジェクトのスコープには注意しましょう。例外クラスのオブジェクトを適切に設計しないと、何らかの後片付けに必要な状態や参照などが足りないと困ります(デストラクタはいつ実行されるかの保証が無いので先に片付ける必要が出るかもしれない)。
C++の例外クラスを使いこなす7つの方法
【C++】std::exceptionの概要と使い方【標準ライブラリ】
最後にWindowsの場合ですが、OS自身の例外との関係を補足しておきます。Windowsは独自の例外処理機構を持っており、これはC言語で書かれていても働きます。この機能とC++の例外の関係は微妙なところがあり、必要に応じたコンパイルオプションを選択することで制御することが出来ます。
構造化例外処理 (C/C++)
C++ での構造化例外の処理
C++ 例外処理と構造化例外処理 (1)
try の外側で例外が発生すると、この機能が働いて他のOSと異なる挙動となることがあって、ビックリすることがあったりします。
なお、例外処理を活用するか否かについては、多くの議論があり、プロジェクトの方針として例外の使用を禁止していることもあります。どうしてそうなるのかというと、例外伝搬の複雑さとパフォーマンスの悪化というのがあり、次回はこの辺りを少し掘ってみようかと思います。
ヘッダ画像は、以下のものを使わせていただきました。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 #throw #catch #例外クラス #継承 #override #noexcept #Windows構造例外
いいなと思ったら応援しよう!
頂いたチップは記事を書くための資料を揃えるために使わせていただきます!