C++ try catch 例外処理とは?エラー処理

例外処理とは?

例外処理は、エラー処理に似ている機能です。

例えば、C言語でエラー処理するとしたら、
以下のような感じになります。

int result = func();     // func関数でエラーになると0を返すとする。
if (result == 0) {
   エラー処理
}

上記のようなif文等によるエラー処理記述は、
ソースファイルの至るところに記述されるため、

エラー処理ではない”プログラム本来の記述”と
区別するのが難しくなってくる場合があります。

こういった場合のためにC++では、
エラー処理の役目を、例外処理と言う機能として用意しています。

上記で示したC言語によるエラー処理を、
C++の例外処理を使って表現すると以下のような感じになります。

int result = 0;

try {
      result = func();
      if (result == 0) {
            throw "func関数でエラーが起きました。";
      }
}

catch (char* e) {
      cout << e << '\n';
}

例外処理では、try(トライ)ブロックとcatch(キャッチ)ブロックを
1セットとして記述します。

tryブロックでは、tryブロックで囲われたプログラム中で
例外が発生しているかどうか
(エラー等が起こっているかどうか)チェックします。

つまり、例外(エラー等)が無いかtry(試す)と言う意味です。

例外を発生させる場合には、throw(スロー)キーワードを使います。

tryブロック内の処理で例外(エラー等)が発生した時には、
throwを使い、例外が起こった時の情報として、
文字列や数値、オブジェクトなどを
対応するcatchブロックに届ける事ができます。

この例の場合は、文字列 “func関数でエラーが起きました。”
をcatchに届けます。

throwと言うキーワードは、
例外情報をthrow(投げる)と言う意味です。

tryブロックで例外が発生すると、throw文以降の処理が実行されずに
対応するcatchブロックに処理が移ります。

そして、catchブロックでは、
throwされた例外情報が関数の引数のように渡され、
その情報によっていろんなエラー対応処理を記述する事が可能になります。

catchブロックが処理された後は、
一つのtry・catchブロックを抜け、順番通りにプログラムが進行します。


間接的に例外を発生させる

tryブロック内に直接throw文を書かずに、
tryブロック内の関数の中でthrow文を書く、と言った事も可能です。

void func() {
      ~何かの処理~
      if (エラー発生) {
            throw "func関数でエラーが起きました。";
      }
}

try {
   func();            // tryブロック内にthrowがないが、
}                       // func関数内にthrowがある。

catch (char* e) {
      cout << e << '\n';
}

スポンサーリンク

異なる型による複数の例外処理

throw文では、例外情報として、文字列や数値、オブジェクトなどを
対応するcatchブロックに届ける事ができます。
と説明しました。

それぞれの異なる型による例外情報は、
それぞれの型に対応するcatchブロックを記述して対応します。

例えば、例外発生時の情報がint型の数値なら
int型の引数を持つcatchブロックに情報が渡されます。

try {
      if (エラー1) throw 1;
      if (エラー2) throw "エラー2";
}

catch (int e) {
      cout << e << '\n';
}

catch (char* e) {
      cout << e << '\n';
}

この場合、整数値による例外情報と、
文字列による例外情報がthrow文で投げられる可能性があるので、
catchブロックは、対応するために、
int型・char*型に対応する2つのcatchブロックを記述しています。

整数値が投げられたら、整数値対応のcatchブロックへ、
文字列が投げられたら、文字列対応のcatchブロックへ、
処理が移ります。


その他の型による例外処理

前述では、整数型と文字列型による例外処理を示しましたが、
どの型にも適さない型が投げられた場合、どうなるでしょうか。

結果は強制終了してしまいます。

こんな時のために、
どんな型情報でも受け取れるcatchブロックの記述方法があります。

try {
      if (エラー1) throw 1;
      if (エラー2) throw "エラー2";
      if (エラー3) throw 1.23;
}

catch (int e) {
      cout << e << '\n';
}

catch (char* e) {
      cout << e << '\n';
}

catch (...) {
      cout << "不明な例外が発生。" << '\n';
}

このプログラムでは、
エラー3の時に小数型の数値が投げられています。

しかし、小数型に対応するcatchブロックは記述されていません。

ですが、catchブロックの引数にと書く事により、
switch文のdefault句のような役目を果たします。

つまり、例外情報がint型のcatchブロックと
char*型のcatchブロックのどちらにも当てはまらない時は、
…と記述されているcatchブロックに処理が移ります。

この場合、引数(例外発生時の情報)は受け取れません。

この…と記述するタイプのcatchブロックは
複数のcatchブロックの最後に書きます。


catchブロックの引数名の省略

これまでの説明では、
catchブロックに引数のタイプと引数名を記述してきましたが、
引数名は省略する事もできます。

省略する事により、その引数を使う事はできなくなりますが、
catchブロック内でその引数を使わない場合には効率的です。

try {
      if (エラー1) throw 1;
      if (エラー2) throw "エラー2";
      if (エラー3) throw 1.23;
}

catch (int) {
      cout << "int型例外が発生" << '\n';
}

catch (char*) {
      cout << "char*型例外が発生" << '\n';
}

catch (...) {
      cout << "不明な例外が発生" << '\n';
}

例外処理のネスト

例外処理ブロックのtry~catchブロックは
多重構造化する事が可能です。

つまり、tryブロックの中にtry~catchブロックがある状態です。

めんどくさそうですが、
1つ1つ見ていけばそれほど複雑ではありません。

// try1
try {
      if (エラー1) throw 1;

      // try2
      try {
            if (エラー2) throw 2;
            if (エラー3) throw 3;
      }

      // catch2
      catch (int i2) {
            cout << "int型例外が発生" << '\n';
            if (i2 == 3) throw;
      }
}

// catch1
catch (int i1) {
      cout << "int型例外が発生" << '\n';
}

このプログラム例の場合は、
まず、try1内で直接throwされた時は、一番外側のcatch1ブロックへ、
try2内で直接throwされた時は、catch2ブロックへ処理が移ります。

次に、エラー3の例外が発生した場合は、
catch2ブロック内のif文に引っかかり、throwされていますが、
“throw”と言う記述だけです。

この記述は、例外の再スローと言い、
1つ上位のcatch1ブロックへ処理が移ります。

この時には、
再スローされたcatch2ブロック内で取得していた引数値も渡されます。

例外の再スローは、現在の場所(catch2ブロック内)では
エラー対応処理するのに必要な情報が足りない。
と言った場合などに用いられます。


例外指定子

try~catchブロックでは様々なデータ型によるthrowを行えますが、
例外指定子と言う機能を使うと、
指定されたデータ型以外のthrowはできなくなります。

また、例外指定子は関数単位で設定します。

void test() throw (int);

void main() {
      test();
}

void test() {
      try {
            if (エラー1) throw 1;
   }

      catch (int) {
            cout << "int型例外が発生" << '\n';
      }
}

このプログラムではtest関数に例外指定子を指定しています。

この場合、throw (int)となっているので、
test関数内でthrowできるデータ型は、int型のみとなります。
それ以外のデータ型によるthrowはできません。

また、複数のデータ型を指定する事も可能です。

void test() throw (int, char*, double);

例外指定子はバグ発生率の低下等に活躍し、
プログラムを、ある程度分かりやすくする効果があります。

また、第3者が作った例外指定子付きの関数を使う場合で、
その関数のエラー処理を行う場合は、
記述されている例外指定子のデータ型だけに対応する例外処理を書けば良い。

と言う意味で受け取れるので、分かりやすいと思います。

例えば、下の例だとtest関数はint型とchar*型を例外として
発生させる可能性があるので、
int型とchar*型を引数としたcatchブロックだけ
記述すればエラー処理できる。と言った具合です。

void test() throw (int, char*);

空のオブジェクトによる例外処理の活用法

例外発生時にスローできるデータ型は、
C++言語の既存データ型(int型等)とユーザー定義型があります。

例外発生時にどういった内容のエラーなのかを
簡潔に知らせる方法として、オブジェクト名を使う方法があります。

特徴としては次の2つです。

・オブジェクト名にエラー内容を推測できる名前を付け、
オブジェクト自体は空にする。

・このオブジェクトは例外処理専用のユーザー定義型として機能させる。

class err1 { };    // 空のオブジェクト
class err2 { };    // 空のオブジェクト

void main() {
      try {
            if (エラー1) throw err1();
            if (エラー2) throw err2();
      }

      catch (err1) {
            cout << "err1例外が発生" << '\n';
      }

      catch (err2) {
            cout << "err2例外が発生" << '\n';
      }

このプログラムでは、空のクラスerr1とerr2が定義されています。

エラー1の時は、
エラー1と言うエラー内容が推測できるerr1型をスローしています。

エラー2の時も同様です。
経験豊富なプログラマが良く使う方法のようです。