C++ エラー処理 例外 書いて覚えるための初心者自己中記事
エラー処理
goto 分
goto ラベル ;
で
ラベル :
にジャンプする。
int main() { const char* error; string line; ifstream file; file.open("test.txt"); if (!file.is_open()) { error = "ファイルを開けませんでした。" ; goto ON_ERROR;//goto ラベル; } getline(file, line); if (file.fail()) { error = "ファイルから読み込めませんでした。"; goto ON_ERROR;//goto ラベル; } cout << line << endl; system("pause"); return EXIT_SUCCESS; ON_ERROR: //ラベル: cout << error << endl; system("pause"); return EXIT_FAILURE; }
ON_ERROR:の前に非エラー時の最終処理を行わないとON_ERRORの処理が行われてしまうので注意。
あと、goto 文は初期化有りの変数宣言の前には置けない。
実際、最初にやったときはそれが原因でエラーになった。
理由はテキストには書いていない。(見落としていなければ)
break; はループを抜けることが出来るが、多重ループでは1階層しか抜けられない。
しかしgoto 文では指定されたラベルまで飛ぶので利用できる。
goto で飛んだ先のラベルの後には空分でもいいので何か書かなければならない。
goto LOOP_END;
~~何かの処理~~
LOOP_END:
; ←空分
こんな感じ。
try
throw
catch
について
try の中で判定してダメなら throw で投げて catch で受け取る。
try の中で複数判定して、ダメならそれぞれ throw で投げて、1つのcatch でthrow の型を受け取る引数を用いて受け取って処理する。
よし。
int main() { try { //try の中で判定して string line; ifstream file; file.open("test.txt"); if (!file.is_open()) { throw "ファイルを開けませんでした。";//ダメだったらthrow で投げて } getline(file, line); if (file.fail()) { throw "ファイルから読み込めませんでした。";//ダメだったらthrow で投げて } cout << line << endl; system("pause"); } catch (const char* error) {//throw を受け取れる型でcatch して処理する cout << error << endl; system("pause"); return EXIT_FAILURE; } }
例外( throw ) が発生しなかった場合は catch は飛び越える。
例外( throw ) が発生したら以降を飛び越えて catch に行く。
tryブロック内で throw を投げるわけだが、tryブロック内で呼び出した関数の中で throw を投げることも出来る。
void Open(ifstream& file, const char* filename) { file.open(filename); if (!file.is_open()) { throw "ファイルを開けません"; } } void GetLine(ifstream& file, string& line) { getline(file, line); if (file.fail()) { throw "ファイルを読み込めません"; } } int main() { try { //try の中で判定して string line; ifstream file; Open(file, "test.txt"); GetLine(file, line); cout << line << endl; system("pause"); } catch (const char* error) {//throw を受け取れる型でcatch して処理する cout << error << endl; system("pause"); return EXIT_FAILURE; } }
tryブロック内で呼び出した関数の中でthrowを投げても大丈夫な理由は
throwが投げられた時にtryブロックがない場合に、throwはtryが見つかるまで関数を抜けて行く性質があるとのこと。
関数を抜けた先にtryブロックを見つけた場合catchに向かうが、throw と catch の型が合わなかった場合はそのcatchも無視してさらに関数をどこまでも抜けていく。
まとめると、throwは自身の型に対応するcatchとくっついているtry をどこまでも探し続ける。(スタック巻き戻しというらしい)
序盤にcatch は一つっぽいことを書いたのに、実は複数OKだった。
1つのtryブロックの後にcatch複数。
ただし、catchの引数の型は別にする。
これでthrowは自分の型にあったcatchに向かってまっしぐら。
あと、型を指定しないcatch があって
catch(...)
というやつ。
この型?であればどんなthrowも受け取れるそう。
ただし、受け取ったとしても(...) だから何もできない。
これはthrowの取りこぼしがないように念のためプログラムの最後に書いたりするものらしい。
catch内でもthrow を投げられる。
void error() { try { throw 1; } catch(int i){ throw "catchから投げたthrow";//ここから投げると } catch (const char* str) { cout << "error関数内" << endl;//ここでは受け取らない system("pause"); } } int main() { try { error(); } catch (const char* str) { cout << "main関数内" << endl;//ここで受け取る system("pause"); } }
catch からthrow が投げられた段階でerror関数内のtryブロックの外に出てるからmain関数内でcatchされたのかな。
あと、if文みたいにネスト出来る。
(これもネストって呼んでいいんだよね・・?)
catchブロック内で
throw;
とやると、catchしたthrow をそのままthrow 出来る。
catch(...) と throw; のコンボが可能。
例外を基底クラスのアップキャストで受け取る。
(派生クラスごとのエラーを一つのcatchで受け取るため)
class.h
class Exception { public: virtual ~Exception(); public: virtual const char* What() const = 0; }; class FileException :public Exception { public: FileException(const char* error); public: virtual const char* What() const; protected: std::string m_error; }; class OpenFileException : public FileException { public: OpenFileException(const char* filenaame); }; class ReadFileException : public FileException { public: ReadFileException(); };
class.cpp
Exception::~Exception() { } FileException::FileException(const char* error):m_error(error) { } const char* FileException::What() const { return m_error.c_str(); } OpenFileException::OpenFileException(const char* filename) :FileException("ファイルを開けませんでした。"){ m_error += "\nファイル名 : "; m_error += filename; } ReadFileException::ReadFileException() : FileException("ファイルから読み込めませんでした。") { }
source.cpp
void Open(ifstream& file, const char* filename) { file.open(filename); if (!file.is_open()) { throw OpenFileException(filename); } } void GetLine(ifstream& file, string& line) { getline(file, line); if (file.fail()) { throw ReadFileException(); } } int main() { try { ifstream file; string line; Open(file, "test.txt"); GetLine(file,line); cout << line << endl; system("pause"); } catch (const FileException& error) { //アップキャストしてる cerr << error.What() << endl; system("pause"); } }
こういった例外のためのクラスは場合ごとに用意されているらしい。
テキストで一覧で書いてあるけど使い方がわからない。
えっとまず、
int main() { try { throw range_error("The range is in error!"); } catch (exception &e) { cerr << "Caught: " << e.what() << endl; cerr << "Type: " << typeid(e).name() << endl; }; system("pause"); }
//output
Caught: The range is in error!
Type: class std::range_error
range_errorクラスとしてthrowしたのを基底クラスのexceptionで参照受け取りしたときに、
typeid(e).name()
メンバ関数 name() でどの例外クラスなのかがわかるみたい。
自分でthrow しなくても勝手にthrow する関数もあるみたいで
int main() { // out_of_range try { string str("Micro"); string rstr("soft"); str.append(rstr, 5, 3); cout << str << endl; } catch (exception &e) { cerr << "Caught: " << e.what() << endl; cerr << "Type: " << typeid(e).name() << endl; }; system("pause"); }
//output
Caught: invalid string position
Type: class std::out_of_range
この場合はstringクラスのメンバ関数 append() は例外を自発的に投げたようだ。
なんかテキストで少し書いてあったなぁ。例外を投げるって。
いろんなタイプの例外クラスがあるけど、受け取った時にエラーの系統がわかるようにって事?
それともなにか重大な理由が・・・?
初心者にはもっと説明してくれないと混乱する・・。
さらに理解できないのが例外指定なるもの。
例外クラスのプロトタイプ宣言時のメンバの最後に
throw();
がついていて、この引数部分に入れた型以外は例外が投げられないというもの。
これの場合は何も型がないから何も例外を投げないとみなされる。
例外指定以外の型で投げるとunexception関数が呼ばれてterminate関数が呼ばれて異常終了するらしい。
そもそもよくわからないのが、例外の基底クラスであるexceptionでメンバがthrow(); になっているとテキストにある。
どういうこと?例外を投げるためのクラスなんじゃないの?
ダメだ、全然わかってない。ちょっと時間を置こう。
コンストラクタ内でthrow を投げた場合、このクラスのデストラクタは呼ばれない。基底クラスやメンバで使用しているクラスのコンストラクタ・デストラクタは呼ばれる。
コンストラクタ内で例外が発生する場合はそのコンストラクタ内でcatchをする。
例外クラスが例外を出すのは対象となる例外のみとしたい。
しかし、例外クラス自体が何らかのエラーを出してしまった場合にも例外が投げられてしまう。
その例外は紛らわしい。
なので例外クラス自身の例外は外にでないようにしている。
そのため、例外クラスのメンバにthrow(); 例外指定がされているらしい。
デストラクタ内で例外を投げてはいけない。
例外はちょっと難しい。
もっとわかりやすいテキスト探そうかな。
とりあえずここまで。