例外処理


スマートなエラー処理

C++では、エラーの処理や管理機能として例外処理 (Exception) がサポートされています
この機能を用いて、実行中の予期せぬエラーに備えます

例外は、try , throw , catch の3つのキーワードで構成されます
まず、例外は明示的に例外を監視しなければなりません
例外が発生する可能性のあるプログラムコードを try ブロックとして { } で囲みます
try {
	//例外が発生する可能性のあるプログラムコード
}
try ブロックないで例外が発生した場合 catch ステートメントでそれを受け取ります
catch ステートメントが適切な例外を受け取り、用意された例外処理を行います
catch (type arg) {
	//例外処理
}
type arg には、関数の宣言のように型と変数名を指定します
catch ブロックは発生した例外が、この型と一致した場合に実行されます
catch は、try ブロックのすぐ後に必ず記述します

例外の発生は throw で行います
このステートメントの構文は return と同じで

throw exception;

exception には、投げる例外の値を指定します
この文が実行されると try ブロックからプログラムは即座に抜け出し
実行するべき catch ステートメントを検索します
#include <iostream>
using namespace std;

int main() {
	try {
		throw "Exception : Kitty on your lap\n";
		cout << "Di Gi Gharat";
	}
	catch (char *str) {
		cout << str;
	}

	return 0;
}
このプログラムは例外処理の実験プログラムです
main() 関数はまず try ブロックを実行し、すぐに throw されます
throw で例外が投げられた時点で try ブロックは終了し catch へ以降します
//そのため Di Gi Gharat は出力されません

throw で投げた例外は文字列の先頭へのポインタです
そのため、catch(char *) がこれを受け取ります
//引数 str は、受け取っても受け取らなくても良いです

throw ステートメントで投げるデータ型は自由です
独自のクラス型も可能であり、実用的な例外では珍しくありません

場合によっては catch でサポートしていない例外が投げられるかもしれません
その場合は標準ライブラリ関数 terminate() が呼び出されます

void terminate();

この関数は例外処理を破棄しなければならない時に呼ばれる関数で
abort() 関数を呼び出してプログラムを以上終了します
#include <iostream>
using namespace std;

int main() {
	try {
		throw "Exception : Kitty on your lap\n";
		cout << "Di Gi Gharat";
	}
	catch (int e) { cout << e; }
	cout << "Kitty on your lap";

	return 0;
}
try ブロックに対する catch ステートメントは int 型を受け取っています
しかし throw しているのは char * 型なのでこれは受け取れません
この時、terminate() が呼び出されてプログラムを終了させます
おそらく、コマンドラインに何らかのエラーメッセージが現れて終了したでしょう

一つの try ブロックがいくつかの例外を投げる可能性がある場合、catch 一つでは不十分ですね
catch ブロックは連続して任意の数だけ用意することができます
#include <iostream>
using namespace std;

int main() {
	int i;
	cout << "0 or 1>";
	cin >> i;

	try {
		if (i) throw "Kitty on your lap";
		else throw 100;
	}
	catch (char *e) { cout << "Exception : " << e << '\n'; }
	catch (int e) { cout << "Exception : " << e << '\n'; }
	return 0;
}
このように、投げられる可能性のある例外に対応した
様々な型の catch ブロックを備えておくことができます

同じ型の catch ブロックを二重定義することはできません
目的の型の catch が実行されると、他の catch ブロックは無視されます

なお、throw される場所は try ブロックでなくてもかまいません
try ブロックから直接、または間接的に呼び出された関数が
throw による例外を投げた場合も catch することができます

例外が発生した場合、プログラムは即座にそのブロックを抜け
スタックをさかのぼって catch ブロックを検索します
#include <iostream>
using namespace std;

void Chobits(bool b) {
	if (b) cout << "Chobits\n";
	else throw "Chobits()";
}

void Kitty(bool b) {
	Chobits(b);
}

int main() {
	try { 
		Kitty(true);
		Kitty(false);
	}
	catch (char *e) { cout << "Exception : " << e << '\n'; }

	return 0;
}
main() 関数の try ブロックで Kitty() 関数を呼び出し
さらにこの関数は Chobits() 関数を呼び出しています
Chobits() で例外が発生すると、プログラムは関数をさかのぼって
最終的に main() 関数の catch() ブロックを検索します

当然、Kitty() で例外が発生しても
関数の呼び出しがいくつネストしても問題なく catch が検出されます
また、Kitty() 関数内で Chobits() が発生された例外を処理することもできます


デフォルトの例外

例外が発生した時、catch が実行されるのは指定した型の例外のみでした
しかし、あらゆる型の例外を受け取ることもできます

全ての例外を受け取るには catch(...) という構文を使います

catch (...) { //例外処理

これは、例えばデフォルトの例外処理の作成に便利です
switch 文の default のような動作を例外で実現することができ
指定した全ての型に合わなかった場合の例外処理として用意することができます
#include<iostream>
using namespace std;

void Kitty(bool b) {
	try {
		if (b) throw 10.1;
		else throw 100;
	}
	catch (double e) { cout << "Exception : " << e << '\n'; }
	catch (...) { cout << "Exception : Kitty()\n"; }
}

int main() {
	Kitty(true);
	Kitty(false);
	return 0;
}
このプログラムの Kitty() 関数は、int 型か double 型の例外を発生させます
このとき、double 型の例外のみ何らかの例外処理を行い
それ以外の型の場合は、一律した処理であるのなら catch(...) が有効です


例外の規制

関数が返すことのできる例外を指定することが可能です
これを指定することで、関数が throw できる型を制限することができます
当然、突き詰めれば例外を返すことのできない関数も作れます

関数に、返すことのできる例外を指定するには
関数の宣言時に、次のような構文を指定します

type function-name(arg-list) throw (exception-type-list) {...

type function-name(arg-list) までは、通常の関数の宣言です
その後に、throw () を指定します
exception-type-list には、返すことのできる型を指定します
複数指定する場合は、カンマで区切って指定し、何も返せないのなら () とすれば良いです

もし、規定以外の型の例外を返した場合は異常終了します
許可のない型の例外が発生した場合、unexpected() を呼び出します

void unexpected();

この関数は terminate() を呼び出して、プログラムを異常終了させます
#include<iostream>
using namespace std;

void Chobits() {
	throw 10.1;
}

void Kitty() throw ( int , char *) {
	Chobits();
}

int main() {
	try { Kitty(); }
	catch (...) { cout << "Exception"; }
	return 0;
}
Kitty() 関数は int , char * 型の例外しか返すことができません
しかし、この関数から呼び出している Chobits() 関数は、double 型の例外を発生させます
そのため、プログラムは unexpected() を呼び出して以上終了します
main() 関数の catch まで制御が戻ることはありません

Kitty() 関数で指定した型以外の例外を発生させた場合
これはコンパイル時に検出できるので、通常はコンパイラが警告を出すはずです


catch から例外を投げる

ある関数内で発生した例外を、その関数で処理し
かつその関数の呼び出し元にも例外が発生したことを伝えたい場合
例外を再び発生させることが可能です

つまり、ある catch ブロックで何からの例外処理をし
さらにその外側の catch ブロックに例外を投げるということです
この場合、 catch ブロックの中で throw することができます

catch ブロックの内部で throw した場合、他の処理は無視され
即座にその外側の catch ブロックの検出が始まります

このとき、throw で発生させるオペランドを省略した場合
その catch ブロックの型の例外が発生したものと見なされます
#include<iostream>
using namespace std;

void Kitty() {
	try { throw "Kitty on your lap\n"; }
	catch (char *) {
		cout << "Exception : Kitty()\n";
		throw;
		cout << "Kitty()\n"; //無視
	}
}

int main() {
	try { Kitty(); }
	catch (char *) { cout << "Exception : main()\n"; }
	return 0;
}
Kitty() 関数は、自分のクラスで発生した char * 型の例外を処理しますが
catch ブロックで throw することで、その外側に再度例外を投げています


newの例外

new 演算子を説明した時に、この演算子はメモリの確保に失敗すると
標準C++ では、例外を発生させるといいました
//古いコンパイラは NULL を返すなど。動作が違います

new演算子は、メモリの割り当てに失敗すると bad_alloc 例外を発生します
これを catch すれば、new の例外に対処することができます
//これに関しても、古いコンパイラは xalloc を返すなど。動作が違います

bad_alloc を使うには <new> を含める必要があります
#include<iostream>
#include<new>
using namespace std;

int main() {
	char *ch;
	int i;

	try {
		for (i = 0 ; ; i++) {
			ch = new char[1048575];
			cout << i << "MB\r";
		}
	}
	catch(bad_alloc) {
		cout << "BAD ALLOC Exception : " << i  << "MB\n";
		return 1;
	}
	return 0;
}
このプログラムは、メモリを解放することなく
ループごとに1MBずつメモリを占領していきます
メモリが割り当てられなくなると、例外が発生して catch で受け取ります


try

例外を監視するブロックを宣言します
tryブロックの後には、必ず catch ブロックを指定します

catch (type)

try ブロックで例外が発生した場合
指定した型と例外の方が会えばブロックを実行します

type - 受け取る例外の型の宣言と変数名を指定します

throw expr

例外を発生させます

expr - 発生される例外の式を指定します

void terminate()

何らかの理由で例外を破棄する必要がある場合に呼び出されます
この関数は、abort() を呼び出してプログラムを以上終了させます

void unexpected();

例外指定で許されない型で例外が発生した時に
制御が関数から離れる場合に呼び出される関数です
デフォルトでは、terminate()を呼び出してプログラムを終了します



前のページへ戻る次のページへ