C++ での例外処理

C++ では、例外は実行時の異常、またはプログラムの実行中に発生する異常な状態です。これらの例外を処理するプロセスは例外処理と呼ばれます。例外処理メカニズムを使用すると、例外が発生したプログラムのある部分からコードの別の部分に制御を移すことができます。

したがって、基本的に C++ の例外処理を使用すると、プログラムが実行し続けるように例外を処理できます。

C++ 例外とは何ですか?

例外とは、プログラムの実行中に発生する予期しない問題であり、プログラムがいくつかのエラーや問題で突然終了します。プログラムの実行中(ランタイム)に例外が発生します。

C++ 例外の種類

C++ には 2 種類の例外があります

  1. 同期: 入力データの間違いにより問題が発生した場合、または数値をゼロで除算するなど、プログラムが処理中の現在のタイプのデータを処理する機能を備えていない場合に発生する例外。
  2. 非同期 : ディスク障害、キーボード割り込みなど、プログラムの制御を超えた例外。

C++ トライアンドキャッチ

C++ には、例外処理用の組み込み機能が用意されています。これは、それぞれ異なる目的を持つ、try、catch、throw という特殊なキーワードを使用して実行できます。

C++ の try-catch の構文

  try   {     // Code that might throw an exception          throw   SomeExceptionType('Error message');  }    catch  ( ExceptionName e1 ) {     // catch block catches the exception that is thrown from try block   } 

1. C++ で試してみる

try キーワードは、try ブロック内に配置された例外をスローする可能性があるコードのブロックを表します。その後に 1 つ以上の catch ブロックが続きます。例外が発生した場合、try ブロックはその例外をスローします。

2. C++ でキャッチする

catch ステートメントは、try ブロックから特定の例外がスローされたときに実行されるコードのブロックを表します。例外を処理するコードは catch ブロック内に記述されます。

3. C++ を投入する

C++ の例外は、throw キーワードを使用してスローできます。プログラムが throw ステートメントに遭遇すると、直ちに現在の関数を終了し、スローされた例外を処理するために一致する catch ブロックの検索を開始します。

注記: 複数の catch ステートメントを使用して、try ブロックによってスローされたさまざまなタイプの例外をキャッチできます。

try キーワードと catch キーワードはペアで使用します。try ブロックを使用してコードをテストし、コードが例外をスローした場合は catch ブロックで処理します。

なぜ必要なのか C++での例外処理?

従来のエラー処理と比較した例外処理の主な利点は次のとおりです。

  1. エラー処理コードと通常コードの分離 : 従来のエラー処理コードでは、エラーを処理するための if-else 条件が常に存在します。これらの条件とエラーを処理するコードは、通常のフローと混同されます。これにより、コードが読みにくくなり、保守しにくくなります。 try/catch ブロックを使用すると、エラー処理のコードが通常のフローから分離されます。
  2. 関数/メソッドは、選択した例外のみを処理できます。 : 関数は多くの例外をスローする可能性がありますが、それらの一部を処理することを選択する場合があります。スローされるがキャッチされない他の例外は、呼び出し元によって処理できます。呼び出し元が例外をキャッチしないことを選択した場合、例外は呼び出し元の呼び出し元によって処理されます。
    C++ では、関数は throw キーワードを使用してスローする例外を指定できます。この関数の呼び出し元は、何らかの方法で例外を処理する必要があります (例外を再度指定するか、キャッチすることによって)。
  3. エラーの種類のグループ化 : C++ では、基本型とオブジェクトの両方が例外としてスローされることがあります。例外オブジェクトの階層を作成し、名前空間またはクラスで例外をグループ化し、タイプに応じて分類することができます。

C++ での例外処理の例

次の例は、try-catch ブロックを使用して C++ で例外を処理する方法を示しています。

例1

以下の例は、C++ での例外のスローを示しています。

C++




// C++ program to demonstate the use of try,catch and throw> // in exception handling.> #include> #include> using> namespace> std;> int> main()> {> > // try block> > try> {> > int> numerator = 10;> > int> denominator = 0;> > int> res;> > // check if denominator is 0 then throw runtime> > // error.> > if> (denominator == 0) {> > throw> runtime_error(> > 'Division by zero not allowed!'> );> > }> > // calculate result if no exception occurs> > res = numerator / denominator;> > //[printing result after division> > cout < <> 'Result after division: '> < < res < < endl;> > }> > // catch block to catch the thrown exception> > catch> (> const> exception& e) {> > // print the exception> > cout < <> 'Exception '> < < e.what() < < endl;> > }> > return> 0;> }>

出力

Exception Division by zero not allowed! 

例 2

以下は、C++ での例外処理を示す簡単な例です。プログラムの出力は、try/catch ブロックの実行フローを説明します。

CPP




// C++ program to demonstate the use of try,catch and throw> // in exception handling.> #include> using> namespace> std;> int> main()> {> > int> x = -1;> > // Some code> > cout < <> 'Before try '> ;> > // try block> > try> {> > cout < <> 'Inside try '> ;> > if> (x <0) {> > // throwing an exception> > throw> x;> > cout < <> 'After throw (Never executed) '> ;> > }> > }> > // catch block> > catch> (> int> x) {> > cout < <> 'Exception Caught '> ;> > }> > cout < <> 'After catch (Will be executed) '> ;> > return> 0;> }>

出力

Before try Inside try Exception Caught After catch (Will be executed) 

C++ における例外処理のプロパティ

プロパティ 1

「catch-all」ブロックと呼ばれる特別な catch ブロックがあり、catch(…) として記述され、すべてのタイプの例外をキャッチするために使用できます。

次のプログラムでは int が例外としてスローされますが、int に対する catch ブロックがないため、catch(…) ブロックが実行されます。

CPP




// C++ program to demonstate the use of catch all> // in exception handling.> #include> using> namespace> std;> int> main()> {> > // try block> > try> {> > // throw> > throw> 10;> > }> > // catch block> > catch> (> char> * excp) {> > cout < <> 'Caught '> < < excp;> > }> > // catch all> > catch> (...) {> > cout < <> 'Default Exception '> ;> > }> > return> 0;> }>

出力

Default Exception 

プロパティ 2

プリミティブ型の場合、暗黙的な型変換は行われません。

次のプログラムでは、「a」は暗黙的に int に変換されません。

CPP




//// C++ program to demonstate property 2: Implicit type> /// conversion doesn't happen for primitive types.> // in exception handling.> #include> using> namespace> std;> int> main()> {> > try> {> > throw> 'a'> ;> > }> > catch> (> int> x) {> > cout < <> 'Caught '> < < x;> > }> > catch> (...) {> > cout < <> 'Default Exception '> ;> > }> > return> 0;> }>

出力

Default Exception 

出力:

Default Exception 

プロパティ 3

例外がスローされてもどこにもキャッチされなかった場合、プログラムは異常終了します。

次のプログラムでは、char がスローされますが、char をキャッチするための catch ブロックがありません。

CPP




// C++ program to demonstate property 3: If an exception is> // thrown and not caught anywhere, the program terminates> // abnormally in exception handling.> #include> using> namespace> std;> int> main()> {> > try> {> > throw> 'a'> ;> > }> > catch> (> int> x) {> > cout < <> 'Caught '> ;> > }> > return> 0;> }>

出力

terminate called after throwing an instance of 'char' 

予期しない関数を作成することで、この異常終了動作を変更できます。

注記 : 派生クラス例外は、基本クラス例外の前にキャッチされる必要があります。

Java と同様に、C++ ライブラリには 標準例外 class は、すべての標準例外の基本クラスです。標準ライブラリのコンポーネントによってスローされるすべてのオブジェクトは、このクラスから派生します。したがって、この型をキャッチすることで、すべての標準例外をキャッチできます。

プロパティ 4

Java とは異なり、C++ ではすべての例外がチェックされません。つまり、コンパイラは例外がキャッチされたかどうかをチェックしません (「 これ 詳細については)。したがって、関数宣言ですべてのキャッチされなかった例外を指定する必要はありません。ただし、例外処理を行うことをお勧めします。

次のプログラムは正常にコンパイルされますが、理想的には、 fun() のシグネチャに未チェックの例外がリストされる必要があります。

CPP




// C++ program to demonstate property 4 in exception> // handling.> #include> using> namespace> std;> // This function signature is fine by the compiler, but not> // recommended. Ideally, the function should specify all> // uncaught exceptions and function signature should be> // 'void fun(int *ptr, int x) throw (int *, int)'> void> fun(> int> * ptr,> int> x)> {> > if> (ptr == NULL)> > throw> ptr;> > if> (x == 0)> > throw> x;> > /* Some functionality */> }> int> main()> {> > try> {> > fun(NULL, 0);> > }> > catch> (...) {> > cout < <> 'Caught exception from fun()'> ;> > }> > return> 0;> }>

出力

Caught exception from fun() 

上記のコードをより適切に記述する方法は次のとおりです。

CPP




// C++ program to demonstate property 4 in better way> #include> using> namespace> std;> // Here we specify the exceptions that this function> // throws.> void> fun(> int> * ptr,> int> x)> throw> (> > int> *,> int> )> // Dynamic Exception specification> {> > if> (ptr == NULL)> > throw> ptr;> > if> (x == 0)> > throw> x;> > /* Some functionality */> }> int> main()> {> > try> {> > fun(NULL, 0);> > }> > catch> (...) {> > cout < <> 'Caught exception from fun()'> ;> > }> > return> 0;> }>

出力

Caught exception from fun() 

注記 注:動的例外仕様の使用は、C++11 以降非推奨になりました。その理由の 1 つは、プログラムがランダムに中止される可能性があることです。これは、動的例外仕様に記載されていない別のタイプの例外をスローした場合に発生する可能性があります。このシナリオでは、デフォルトで abort() を呼び出す terminate() を (間接的に) 呼び出すため、プログラム自体が中止されます。

プロパティ 5

C++ では、try/catch ブロックをネストできます。また、 throw を使用して例外を再スローすることもできます。 。

次のプログラムは、try/catch ブロックのネストを示しています。

CPP




// C++ program to demonstrate try/catch blocks can be nested> // in C++> #include> using> namespace> std;> int> main()> {> > // nesting of try/catch> > try> {> > try> {> > throw> 20;> > }> > catch> (> int> n) {> > cout < <> 'Handle Partially '> ;> > throw> ;> // Re-throwing an exception> > }> > }> > catch> (> int> n) {> > cout < <> 'Handle remaining '> ;> > }> > return> 0;> }>

出力

Handle Partially Handle remaining 

関数は、同じスローを使用して関数を再スローすることもできます。構文。関数は一部を処理し、呼び出し元に残りの処理を依頼できます。

特性6

例外がスローされると、コントロールが catch ブロックに転送される前に、囲んでいる try ブロック内で作成されたすべてのオブジェクトが破棄されます。

次のプログラムは、上記のプロパティを示します。

CPP




// C++ program to demonstrate> #include> using> namespace> std;> // Define a class named Test> class> Test {> public> :> > // Constructor of Test> > Test() { cout < <> 'Constructor of Test '> < < endl; }> > // Destructor of Test> > ~Test() { cout < <> 'Destructor of Test '> < < endl; }> };> int> main()> {> > try> {> > // Create an object of class Test> > Test t1;> > // Throw an integer exception with value 10> > throw> 10;> > }> > catch> (> int> i) {> > // Catch and handle the integer exception> > cout < <> 'Caught '> < < i < < endl;> > }> }>

出力

Constructor of Test Destructor of Test Caught 10 

C++ における例外処理の制限

C++ の例外処理にもいくつかの制限があります。

  • コード内に複数の目に見えない終了ポイントが作成され、コードの読み取りやデバッグが困難になるため、例外によってコードの構造やフローが破壊される可能性があります。
  • 例外処理が適切に行われない場合、リソース リークが発生する可能性もあります。
  • 安全な例外コードの書き方を学ぶのは困難です。
  • 例外処理の使用方法に関する C++ 標準は存在しないため、例外処理の実践には多くのバリエーションが存在します。

結論

C++ の例外処理は、try ブロックと catch ブロックを使用して予期せぬ事態を処理し、問題を効率的に管理するために使用されます。この例外処理により、実行時のエラーを個別に処理できるため、プログラムの信頼性が向上します。また、エラー発生時のプログラムのクラッシュや突然の終了を防ぐことにも役立ちます。

関連記事:

  • C++ 例外処理に関するトップのインタビューの質問と回答
  • C++ の例外処理に関するクイズ