C ++では、エラーコードを返すだけでなく、例外とtry / catchを使用する利点は何ですか?
-
10-07-2019 - |
質問
私は長い間CとC ++をプログラミングしてきましたが、これまでのところ、例外とtry / catchを使用したことはありません。関数にエラーコードを返すだけでなく、それを使用する利点は何ですか?
解決
おそらく明白なポイント-開発者はあなたの返品ステータスを無視するか(または気付かない)、何かが失敗したことを至福の気づかずに続けることができます。
何らかの方法で例外を確認する必要があります。積極的に何かを適切に配置することなく静かに無視することはできません。
他のヒント
例外の利点は2つあります:
-
これらは無視できません。何らかのレベルで対処する必要があります。対処しないと、プログラムが終了します。エラーコードがある場合は、それらを明示的に確認する必要があります。そうしないと失われます。
-
これらは無視できます。あるレベルでエラーを処理できない場合、次のレベルに自動的にバブルします。エラーコードは、対処可能なレベルに達するまで明示的に渡す必要があります。
利点は、呼び出しが失敗する可能性があるたびにエラーコードを確認する必要がないことです。ただし、これが機能するためには、RAIIクラスと組み合わせて、スタックが巻き戻されるときにすべてが自動的にクリーンアップされるようにする必要があります。
エラーメッセージあり:
int DoSomeThings()
{
int error = 0;
HandleA hA;
error = CreateAObject(&ha);
if (error)
goto cleanUpFailedA;
HandleB hB;
error = CreateBObjectWithA(hA, &hB);
if (error)
goto cleanUpFailedB;
HandleC hC;
error = CreateCObjectWithA(hB, &hC);
if (error)
goto cleanUpFailedC;
...
cleanUpFailedC:
DeleteCObject(hC);
cleanUpFailedB:
DeleteBObject(hB);
cleanUpFailedA:
DeleteAObject(hA);
return error;
}
例外とRAIIを使用
void DoSomeThings()
{
RAIIHandleA hA = CreateAObject();
RAIIHandleB hB = CreateBObjectWithA(hA);
RAIIHandleC hC = CreateCObjectWithB(hB);
...
}
struct RAIIHandleA
{
HandleA Handle;
RAIIHandleA(HandleA handle) : Handle(handle) {}
~RAIIHandleA() { DeleteAObject(Handle); }
}
...
一見、RAII / Exceptionsバージョンは、クリーンアップコードを1回だけ記述する必要があることに気付くまでは長く見えます(そして、それを単純化する方法があります)。しかし、DoSomeThingsの2番目のバージョンは、より明確で保守可能です。
RAIIイディオムなしでC ++で例外を使用しようとしないでください。リソースとメモリがリークするためです。すべてのクリーンアップは、スタックに割り当てられたオブジェクトのデストラクターで行う必要があります。
エラーコードを処理する方法は他にもありますが、それらはすべて同じように見えます。 gotoをドロップすると、クリーンアップコードの繰り返しになります。
エラーコードの1つのポイントは、どこで失敗するか、どのように失敗するかを明らかにすることです。上記のコードでは、物事が失敗しないという前提で記述します(しかし、失敗した場合、RAIIラッパーによって保護されます)。しかし、あなたは物事がうまくいかない可能性のある場所に注意を払うことになります。
例外処理は、プログラムの機能を処理するために記述されたコードからエラー処理コードを簡単に分離できるため便利です。これにより、コードの読み取りと書き込みが簡単になります。
言及された他のことは別として、コンストラクタからエラーコードを返すことはできません。デストラクタも使用できますが、デストラクタから例外をスローすることは避けてください。
- 場合によっては、エラー状態が expected の場合にエラーコードを返します
- どのような場合でもエラー状態が予想されない ときに例外をスローする
前者の場合、関数の呼び出し元は、予想されるエラーのエラーコードをチェックする必要があります。後者の場合、例外はスタックの呼び出し元(またはデフォルトのハンドラー)が適切に処理できます
これについてブログエントリを書きました(例外作成エレガントコードの場合)、その後オーバーロードで公開されました。実際、JoelがStackOverflowポッドキャストで言ったことに応えてこれを書きました!
とにかく、ほとんどの状況でエラーコードよりも例外の方が望ましいと強く信じています。エラーコードを返す関数を使用するのは本当に苦痛です。呼び出しごとにエラーコードを確認する必要があり、呼び出しコードのフローを混乱させる可能性があります。また、エラーを通知する方法がないため、オーバーロードされた演算子を使用できないことも意味します。
エラーコードをチェックすることの苦痛は、人々がしばしばそうすることを怠ることを意味し、したがってそれらを完全に無意味にします:少なくとも catch
ステートメントで例外を明示的に無視する必要があります。
例外が存在する場合にリソースが正しく解放されるようにするために、C ++のデストラクタと.NETのディスポーザを使用すると、コードを大幅に簡素化できます。エラーコードで同じレベルの保護を得るには、多数の if
ステートメント、重複したクリーンアップコード、または次のクリーンアップの共通ブロックへの goto
呼び出しが必要です。関数の終わり。これらのオプションはどれも快適ではありません。
ここに EAFPの適切な説明があります("許可。")。これは、WikipediaのPythonページであっても、ここで適用されると思います。例外を使用すると、より自然なスタイルのコーディング、IMO、そして他の多くの意見にもつながります。
私がC ++を教えていたとき、私たちの標準的な説明は、晴れた日と雨の日のシナリオが絡まるのを避けることができるというものでした。つまり、すべてが正常に機能するかのように関数を記述し、最後に例外をキャッチできます。
例外がなければ、各呼び出しから戻り値を取得し、それがまだ正当であることを確認する必要があります。
関連する利点は、もちろん、「無駄」にしないことです。例外の戻り値(したがって、voidである必要があるメソッドをvoidにすることができます)。また、コンストラクターおよびデストラクターからエラーを返すこともできます。
GoogleのC ++スタイルガイドには、優れた、徹底的なC ++コードでの例外使用の長所と短所の分析。また、あなたが尋ねるべき大きな質問のいくつかを示しています。つまり、自分のコードを他の人(例外対応のコードベースと統合するのが困難な人)に配布するつもりですか?
例外的なケースにフラグを立てるために、本当に例外を使用する必要がある場合があります。たとえば、コンストラクターで何かがうまくいかず、これについて呼び出し元に通知することが理にかなっている場合、例外をスローする以外に選択肢はありません。
別の例:関数がエラーを示すために返すことができる値がない場合があります。関数が返す可能性のある値は成功を示します。
int divide(int a, int b)
{
if( b == 0 )
// then what? no integer can be used for an error flag!
else
return a / b;
}
例外を確認する必要があるという事実は正しいですが、これはエラー構造体を使用して実装することもできます。 特定のメソッド(IsOkなど)が呼び出されたかどうかをdtorでチェックする基本エラークラスを作成できます。そうでない場合は、何かをログに記録してから終了するか、例外をスローするか、アサートを発生させるなど...
エラーオブジェクトに応答せずにIsOkを呼び出すだけで、catch(...){}を記述するのと同じになります。 両方のステートメントは、プログラマーの善意の同じ欠如を示します。
エラーコードの正しいレベルまでの転送は、より大きな懸念事項です。基本的に、伝播の唯一の理由で、ほとんどすべてのメソッドがエラーコードを返すようにする必要があります。 ただし、この場合も、関数またはメソッドには、生成できる例外を常に注釈する必要があります。そのため、基本的には、同じ問題をサポートしなければなりません。
@Martinが指摘したように、例外のスローはプログラマにエラーの処理を強制します。たとえば、リターンコードをチェックしないことは、Cプログラムのセキュリティホールの最大の原因の1つです。例外により、エラーを処理すること(できれば)を確認し、プログラムに何らかの回復パスを提供します。また、セキュリティホールを導入するのではなく、例外を無視することを選択した場合、プログラムがクラッシュします。