スポンサードリンク
HOME > C++言語 > 例外処理とは?

C++言語 - 例外処理とは?

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

例えば、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ブロックが処理された後は、一つのtrycatchブロックを抜け、

順番通りにプログラムが進行します。



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

 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';
}




例外処理のネスト

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

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

めんどくさそうですが、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ブロック内)では

エラー対応処理するのに必要な情報が足りない。と言った場合などに用いられます。



例外指定子

trycatchブロックでは様々なデータ型による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';
   }



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

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

エラー2の時も同様です。

経験豊富なプログラマが良く使う方法のようです。



スポンサードリンク







HOME

言語解説編
プログラミング用語・知識

C言語

C++言語

Java

C#

Visual Basic.NET

アプリケーション編
C言語による
コンソールプログラミング




Copyright (C) プログラミングランド All Rights Reserved