例外処理編集

例外処理とは編集

プログラムのコードを記すとき、本来ならプログラム実行時に起きてはいけないエラーを想定して、コードを書く必要がある。

そのため、たとえば、もし、ある変数mが本来なら10以上になる場合なら、その変数mが10未満(つまり9.9や8など)になった場合には、エラーである事を表示する必要がある。

このような処理を例外処理という。

例外処理のコードは、次のようになる。

try{
  例外の起こりそうな部分;
}

catch(int i){
   例外発生時の処理;
}

まず、上記のコードのように、tryにより、例外の起きそうな部分を{ }で囲み、例外の起きそうな部分をtry文ブロックとする必要がある。そして、try文のブロックのあとに、catch文のブロックを書く必要がある。

もしtry文の前にcatch文を書いても、コンパイル試行時にエラーになり、コンパイルできない。また、try文の前と後ろにそれぞれcatch文を書いても、これもコンパイル時にエラーになるので、コンパイルできない。

よって、catch文はかならず、try文よりも後ろにある事になる。


しかし、単にtry文とcatch文を書いただけでは、まだ、C++では例外処理をできない。

例外処理をできるようにするためには、さらに、次に説明する throw スロー が、C++での例外処理のために必要である。

try{
   throw 2;
}

catch(int i){
   cout << "エラーが発生しました。";
}

try文のブロック内に、throwを書く必要がある。 なお英語で throw とは、「投げる」という意味の単語であり、発音はスローと読む(スポーツとかでも、ボールを投げることをスローというだろう)。ともかく、プログラム中でthrowが実行されると強制的にcatch文に制御が移る。


なお、もしtryのブロック外にthrowを書いても、コンパイルできてしまう場合もあるが、しかし、コンパイルできてしまった場合でも、オブジェクトファイルの実行動作中にエラーになり異常終了しかねないので、けっしてtryブロック外にthrowを書いてはいけない。

とにかく、tryブロック内でだけthrowを書けば良い。

本wikiboosでは、特にことわらないかぎり、throwはtryブロックの中でだけ書かれるとする。

なので通常、try文のブロック内に、throwは書かれる。

しかし、このコードでは、パソコンはまだ何も、ある状態が「例外」であるかを判断できない。

なぜなら、tryやcatchやthrowという命令そのものには、ある処理が「例外である」かどうかを判断する機能は無いし、「エラーである」かどうかを判断する機能もない。もちろん、if文のような条件分岐などの機能も、ない。

人間が勝手に、tryとcatchとthrowを用いたコードに、「例外処理」という意味付けをしているだけである。

tryやcatchやthrowは、例外処理のために作られた機能だろう。しかし、C++言語では、例外処理のためには、さらに、別の命令(たとえばif文など)を使用する必要がある。

上記のコードだけでは、単に、throwが実行されて、それによって、catchが実行されるだけであり、「エラーが発生しました。」と表示するだけである。tryやcatchやthrowという3つの命令は、単に、部品である。なんの部品か言うと、例外処理を行えるコードを作るための部品である。この3つの部品(tryとcatchとthrow)だけでは、普通は、なにひとつとして、ある状態が「例外であるか?」どうかの判断をできない。

そのため、実務的には、さらにif文などと組み合わせることにより、例外であるかどうかの判断をする。(下記の節で解説する。)

要するに、実務的にはコードの記述の際は、if文などを活用することで、throw命令の置かれているブロックが、例外の発生時の場合にだけ、実行されるように、コードを記述する必要がある。

  • 備考

throwによって、cathe文に制御が移ることを、「例外を投げる」などと言う場合もある。


「例外」という場合わけ編集

例外発生という「場合わけ」を行うわけだから、「場合わけ」のためにif文などを使用することになる。

そのため、例外発生の「場合わけ」を行えるコードは、下記のようなコードになる。

try{
  例外の起こりそうな部分
  if (条件) {
   throw 2;
  }
}

catch(int i){
   cout << "エラーが発生しました。";
}


上記のコードのように、例外であるかどうかの判断をできるようにするためには、 そして、そのif文の条件は、プログラム正常時なら真にならないハズの条件を書く必要がある。

正常時なら、if文の条件が真にならないので、よってifブロック内のthrowは実行されない。そして、throwが実行されないので、catchブロックも実行されない。こうして、上記のようなコードに、正常時なら「エラーが発生しました。」を表示させないようにすることができる。


なお、throwによって、強制的にcatchに制御が移動するため、throw後に書かれたコードは実行されない。

try{
  例外の起こりそうな部分
  if (条件) {
   throw 2;
   cout << "ああああ"; //ここは実行されない。
  }
}

つまり、上記のコードにある「ああああ」は、プログラム実行時には表示されない。

throwによって、catchにエラーが送出される。tryからcatchへのエラーの送出は、変数データや文字列データなどの送出として実行される。上記のコードの例の場合では、数値「2」がtryからcatchへと送出される。

なので、tryの直後に送出されるデータとしての変数データ(または文字列データなど)を書く必要があり、そのtry直後の変数データ(または文字列データなど)がcacthへと送出される。

tryから送出されたデータの型と、catch()の丸カッコ内に記述されたデータの型が同じなら、そのcatch文に記述された処理内容が実行される。

上記のコードの場合なら、tryから数値「2」が送られてきたので、catch()の丸カッコ内には、int型が書かれている。

つまり、catch文に書かれた処理を実行させたい場合には、そのcatch()の丸カッコ内には、tryから送出されたデータと同じ型を記述しなければならない。

if文はなくてもよい編集

tryブロック内にif文がなくても、throwさえあれば、catchブロックにエラーを送出できる。 if文のない場合、次のような文例になる。

try{
   throw 2;
}

catch(int i){
   cout << "エラーが発生しました。";
}

つまり、throwとは、単にtryからcatchに、エラーの意味として解釈される事になるデータを送るだけの命令である。 throw自体には、条件分岐の機能はない。(もちろん、try自体にも、条件分岐の機能はない。)

tryやthrowやcatchという命令そのものには、ある処理が「例外である」かどうかを判断する機能は無い。 人間が勝手に、tryとcatchを用いたコードに、「例外処理」という意味付けをしているだけである。

つまり、上記のコードの場合、なにも条件分岐が行われていないので、かならずthrowが実行されるので、かならずcatchブロックの処理が発生する事になる。

では読者は、実際に次のコードをコンパイルしてみて、「まだ投げてない状態です。」と「エラーが発生しました。」が表示されることを、確認してみよう。

#include <iostream>
using namespace std;

int main()
{
   try{
     cout << "まだ投げてない状態。" << endl;
     throw 2;
     cout << "ここは実行されない。" << endl;
   }

   catch(int i){
     cout << "エラーが発生しました。" << endl;
   }
}

上記のコードをコンパイルして実行しましたか? 実行してみて、「まだ投げてない状態です。」と「エラーが発生しました。」が表示されることを確認してみましたか? 「ここは実行されない」は表示されないことを確認しましたか?

確認したら、次の節へと進んでください。

実例編集

if文をもちいた文例編集

なお、さらにif文を実際の例外処理では使用するので、if文とtry,throw,catchの例外処理は、次のように組み合わされる事が多いだろう。

#include <iostream>
using namespace std;

int main()
{
   try{
     if(1) {
         throw 2;
           }
   }

   catch(int i){
     cout << "エラーが発生しました。" << endl;
   }
}

なお、上記のコードでは、エラー時の挙動を確認するために、if文の条件を1にすることで、わざとエラーを起こしている。

本来なら、throw命令を内部にもつif文ブロックの条件は、成立しないほうが望ましいだろう。


なお、上記のコードは、仕組みを分かりすくするためにif文を複数行に分割して書いている。しかし、実際のコードでは、throwするだけの場合なのに複数行に分割するのは不合理なので、次のように、if文をひとつの行にまとめるのが普通だろう。

#include <iostream>
using namespace std;

int main()
{
   try{
     if(1) { throw 2; }
   }

   catch(int i){
     cout << "エラーが発生しました。" << endl;
   }
}

if文の条件式が成り立たない場合、当然ながら、if文中のthrowは、けっして実行されない。 では、実際に、下記のコードをコンパイルして実行してみて、確認してみよう。下記のコードでは、if文のコードがif(0)となっているように、if文の条件がつねに「偽」である。

#include <iostream>
using namespace std;

int main()
{
   cout << "まだ投げてない状態。" << endl;
   try{
     if(0) { throw 2; }
   }

   catch(int i){
     cout << "エラーが発生しました。" << endl;
   }
}

上記のプログラムを実行しても、文「エラーが発生しました。」は、表示されずに終了する。文「まだ投げてない状態。」だけが表示される。

if文のない文例編集

tryとthrowとcatchを用いただけのコードの基本的な文例は、一例として、次のような構造になります。

#include <iostream>
using namespace std;

int main()
{
   try{
     throw 2;
   }

   catch(int i){
     cout << "エラーが発生しました。" << endl;
   }
}


C++言語のtryはエラー判断を行わない編集

C++言語では、tryキーワードは、エラー判断を行わない。(他のプログラミング言語では、try構文がエラー判断を行うプログラミング言語もある。しかしC++言語では、tryキーワードはエラー判断を行わない。)

そのためC++言語で、次のような割り算のプログラムで、0で割り算をすることになるようなエラーになる数値を代入しても、 コード中にthrowがないので、catchブロック内のコードは、なにも実行されない。

#include <iostream>
using namespace std;

int main()
{
   cout << "まだ投げてない状態。" << endl;
	try{
	double a, b;
	cout << "数字bを入力してください。3をその数字で割り算します。" << endl;
	cin >> b;
	a= 3.0 / b ;
	cout << "3÷b = " << a << endl;
	cout << "計算が終わりました。" << endl;
   }

   catch(int i){
	cout << "エラーが発生しました。" << endl;
	exit(EXIT_FAILURE);
   }
}

読者は、上記のコードを実行してみて、数値入力を求められるので、0を入力してみよう。すると、catchブロック内にある「エラーが発生しました。」は表示されない。いっぽう、tryブロック内にある「計算が終わりました。」は表示され、計算が終わるとともにプログラムも終わる。


まとめ編集

今までの話をまとめると、例外処理の構文は、次のようになる。

   try{
     例外の起こりそうな部分;
   }

   catch( 変数名){
     例外発生時の処理;
   }

複数のcatch文のあるときの例外処理編集

catch文が複数あるとき、投げられた例外は、上の行から順にcatchで受け取れるかどうかが判定され、さいしょに型の一致したcatch文で受け取られたときに、その例外は消失する。そして、その例外は、もし、次の行のcatchでも型が一致しても、もう例外が消失してるので受け取らられない。

なので、たとえば、次のようにcatch文が複数あるとき、実行してみると・・・

#include <iostream>
using namespace std;

int main()
{
   try{
     throw 10;
     cout << "ここは実行されない。" << endl;
   }

   catch(int i){
     cout << "エラー1が発生しました。" << i << endl;   }
   catch(int i){
     cout << "エラー2が発生しました。" << i << endl;   }
}
実行結果
エラー1が発生しました。10

このように、さいしょのcatch文だけが実行される。


catch(int a)のように、引数の型を整数型に指定する事によって、例外が整数型で投げられた場合にだけ、キャッチする事ができる。

つまり、catch(int a)の場合のは、例外がthrow 6.123 のように浮動小数型で例外が投げられても、catch(int a)はその浮動小数型の例外を無視する。

6.123を投げた例外を受け取るには、catchは、catch(double d)でなければならない。


このようにして、例外の型を分けることにより、複数のtry文を、対応した型を引数にもつcatch文により受け取らせる事ができ、それぞれのcatch構文に制御を移すという仕組みになっている。


#include <iostream>
using namespace std;

int main()
{
   try{
     throw 12.345;
     cout << "ここは実行されない。" << endl;
   }

   catch(int i){
     cout << "エラー1が発生しました。" << i << endl;   }
   catch(double i){
     cout << "エラー2が発生しました。" << i << endl;   }
}
実行結果
エラー2が発生しました。12.345

このように、引数の型の一致しないcatch(int i)は無視される。そして、catch(double i)のみが実行される。


例外は文字型(char型)であってもいい。

#include <iostream>
using namespace std;

int main()
{
   try{
     throw 'a';
     cout << "ここは実行されない。" << endl;
   }

   catch(int i){
     cout << "エラー1が発生しました。" << i << endl;   }
   catch(char i){
     cout << "エラー2が発生しました。" << i << endl;   }
}
実行結果
エラー2が発生しました。a



なお、 catch(...)文 により、その文に到達するまで受け取られなかった例外すべてを受け取る。

#include <iostream>
using namespace std;

int main()
{
   try{
     throw 5.432;
     cout << "ここは実行されない。" << endl;
   }

   catch(int i){
     cout << "エラー1が発生しました。" << i << endl;   }
   catch(...){
     cout << "エラー2が発生しました。" << endl;   }
}

なお、このとき

   catch(...){
     cout << "エラー2が発生しました。" << i <<endl;   }
}

のように書くと、「引数iが宣言されていません」的な内容のエラーメッセージが出て、コンパイルできない。


exit関数の追加編集

さて、実務的には、catchブロックのコードの最後に、exit()関数を書いて、プログラムを終了させる事が多い。 なお、exit関数は、べつに例外処理だけにかぎった機能ではない。つまり、catchブロック内でなくても、exit関数は使用できる。

では読者は、次のコードをコンパイルして実行してみて、exit関数を試してみよう。

#include <iostream>
using namespace std;

int main()
{
   try{
     cout << "まだ投げてない状態。" << endl;
     throw 2;
     cout << "ここは実行されない。" << endl;
   }

   catch(int i){
     cout << "エラーが発生しました。" << endl;
     exit(EXIT_FAILURE);
     cout << "実行されないはずの場所。" << endl;
   }
}

実務的には、catch文のさいごには、そのプログラムを終了させるためのコード(たとえばexit()関数「exit(EXIT_FAILURE)」など)を記述して、データの破壊を防ぐためのコードを書くことが多い。なお、exit関数の引数(上記の場合は「EXIT_FAILURE」が引数)がないと、コンパイルエラーとなる。

上記のコードでは、プログラム処理がexit(EXIT_FAILURE);で終了するので、その次の行のcout << "実行されないはずの場所。" << endl;は実行されない。よって、上記のコードをコンパイルして実行しても、文「実行されないはずの場所。」は画面に表示されない。


では、上記のコードから、実行されないコードを除外しましょう。

#include <iostream>
using namespace std;

int main()
{
   try{
     cout << "まだ投げてない状態。" << endl;
     throw 2;
   }

   catch(int i){
     cout << "エラーが発生しました。" << endl;
     exit(EXIT_FAILURE);
   }
}


※ C++にはfinally文はない編集

よそのプログラム言語には、例外処理の構文で「finally」ファイナリーという構文があるのもあります。例外が発生しても発生しなくても、どちらにせよ実行される処理が、finallyに書かれ、それらの言語では、実際に実行されます。よく、ファイルを閉じる処理が、filally文に書かれることが多いです。(エラーが発生しても発生しなくても、ファイルを閉じる必要があるため。)

しかし、C++には、このfinally文はありません。考えてみれば、そもそもC++で書かれたプログラムは、人間がエラー処理のプログラムをコードで設計しないかぎり、けっしてプログラムは自動的にはエラー判定をしてくれません。なので、プログラマー以外による自動的エラー判定を前提にしたfinally構文は、そもそも、原理的にC++では無理です。


では、finally文のある よそのプログラム言語が、どのようにしてエラー判定を自動的に行っているかというと、それは「ランタイム」(実行環境)などと言われる、そのプログラムを実行するための別のソフトウェアをパソコンにインストールする必要のあるものもあります。


Java(ジャバ)やPyhton(パイソン)やC#(シーシャープ)といった言語に、finally構文があります。

PythonやJavaScripyなどのインタプリタとして動作する言語にfinally文があったりするのも、それはインタプリタ処理系が事実上のランタイムとして機能しているからでしょう。

そのような、ランタイムのある言語では、ランタイムが自動的にエラー判定をしてくれるので、finally文があったりします。


しかしC言語やC++で書かれたプログラムは、そもそも機械語にコンパイルされて使用されるので、そもそもランタイムがなくても動作しなければなりません。(もし、ある言語が、ソースコードをランタイムがないと動作できないコードに変換したのなら、そもそも、その変換後のコードは「機械語」ではない。そういうランタイムなどを必要とする言語に変換した場合の呼び名は、変換後の言語のことを「中間言語」(ちゅうかん げんご)などと呼ぶ。)

なので、そもそも、ランタイムなどによるエラー判定を前提にしたfinally文について、最終的に機械語にコンパイルすることを目指す C++ では、そもそも中間言語的なランタイムを前提にしたfinally文は、存在意味をもちません。

なので、もし今後、C++の規格がもし変更しても、おそらくC++にはfinally文は、けっして追加されないでしょう。


JavaやPythonといったfinally文のあるプログラム言語は、けっしてC言語のようなOSそのものの開発への対応を目指す言語(システムプログラミング言語)ではなくて、アプリケーション開発に使う言語が多いようです。(もし、ある自称「OS」がエラー自動判定などの機能をもつ外部ランタイムがないと開発できないなら、それはもはや「OS」には値しない。)