C ++の悪い習慣でassert()を使用していますか?


93

リリースビルドのパフォーマンスに影響を与えずにデバッグを容易にするために、C ++コードに多くのアサーションを追加する傾向があります。これassertは、C ++メカニズムを考慮せずに設計された純粋なCマクロです。

一方、C ++はを定義しますstd::logic_error。これは、プログラムのロジック(したがって、名前)にエラーがある場合にスローされることを意図しています。インスタンスをスローすることは、の代わりに、C ++に代わる完璧な方法になる可能性がありますassert

問題はassertabort両方ともデストラクタを呼び出さずにプログラムをすぐに終了するため、クリーンアップをスキップするのに対し、例外を手動でスローすると、不要な実行時コストが追加されることです。これを回避する1つの方法は、独自のアサーションマクロを作成することです。これはSAFE_ASSERT、Cの対応物と同じように機能しますが、失敗すると例外をスローします。

この問題について3つの意見を考えることができます。

  • Cの主張に固執する。プログラムはすぐに終了するので、変更が正しく展開されるかどうかは問題ではありません。また、#defineC ++でsを使用することも同様に悪いことです。
  • 例外をスローし、main()でキャッチします。コードがプログラムのどの状態でもデストラクタをスキップできるようにすることは悪い習慣であり、すべての犠牲を払って回避する必要があります。また、terminate()の呼び出しも同様です。例外がスローされた場合は、キャッチする必要があります。
  • 例外をスローし、プログラムを終了させます。 プログラムを終了する例外は問題ありNDEBUGません。これにより、リリースビルドではこれが発生することはありません。キャッチは不要であり、内部コードの実装の詳細をに公開しmain()ます。

この問題に対する明確な答えはありますか?専門的な参考資料はありますか?

編集:デストラクタのスキップは、もちろん、未定義の動作ではありません。


22
いいえ、本当に、logic_error論理エラーです。プログラムのロジックのエラーはバグと呼ばれます。例外をスローしてバグを解決することはありません。
R.マルティーニョフェルナンデス

4
アサーション、例外、エラーコード。それぞれに完全に異なるユースケースがあり、別のユースケースが必要な場合は使用しないでください。
Kerrek SB 2012

5
static_assert利用できる場合は、適切な場所で使用してください。
フレキソ

4
@trionそれがどのように役立つかわかりません。投げstd::bugますか?
R.マルティーニョフェルナンデス

3
@トリオン:それをしないでください。例外はデバッグ用ではありません。誰かが例外をキャッチしている可能性があります。を呼び出すときにUBについて心配する必要はありませんstd::abort()。プロセスを終了させるシグナルを発生させるだけです。
Kerrek SB、2012

回答:


73

アサーションは、C ++コードでは完全に適切です。例外やその他のエラー処理メカニズムは、実際にはアサーションと同じものを対象としていません。

エラー処理は、エラーを回復またはユーザーに適切に報告する可能性がある場合に使用します。たとえば、入力ファイルを読み込もうとしてエラーが発生した場合、それについて何かしたいと思うかもしれません。エラーはバグが原因で発生する可能性がありますが、特定の入力に対して適切な出力である可能性もあります。

アサーションは、APIが通常チェックされないときにAPIの要件が満たされていることを確認するためのものや、開発者が構築によって保証されていると信じているものを確認するためのものです。たとえば、アルゴリズムがソートされた入力を必要とする場合、通常はチェックしませんが、デバッグビルドがその種のバグにフラグを立てるようにチェックするアサーションがある場合があります。アサーションは常に、正しく動作しないプログラムを示す必要があります。


クリーンでないシャットダウンが問題を引き起こす可能性のあるプログラムを作成している場合は、アサーションを回避することをお勧めします。C ++言語に関して厳密に定義されていない動作は、アサーションにヒットしたことがおそらく未定義の動作の結果であるか、または一部のクリーンアップが適切に機能しなくなる可能性がある他の要件に違反しているため、ここではそのような問題とは見なされません。

また、例外の観点からアサーションを実装すると、アサーションの目的そのものと矛盾する場合でも、キャッチされて「処理」される可能性があります。


1
これが答えで具体的に述べられているかどうかは完全にはわからないので、ここで述べます。コードの記述時に決定できないユーザー入力を含むものに対してはアサーションを使用しないでください。ユーザーがコードでは3なくパスする場合、1通常、アサーションはトリガーされません。アサーションはプログラマエラーのみで、ライブラリのユーザーやアプリケーションのエラーではありません。
SSアン

101
  • アサーションはデバッグ用です。出荷されたコードのユーザーは、それらを決して見るべきではありません。アサーションがヒットした場合、コードを修正する必要があります。

  • 例外は例外的な状況用です。遭遇した場合、ユーザーは自分のやりたいことを実行できなくなりますが、別の場所で再開できる可能性があります。

  • エラー処理は、通常のプログラムフロー用です。たとえば、ユーザーに数字の入力を求め、解析できないものを取得した場合、それは正常です。これは、ユーザーの入力が制御下になく、当然のことながらすべての可能な状況を常に処理する必要があるためです。(たとえば、有効な入力が得られるまでループし、その間に「申し訳ありませんが、もう一度やり直してください」と言います。)


1
この再主張を探しに来た; 製品コードへのアサートの通過は、設計とQAが不十分であることを示しています。アサートが呼び出されるポイントは、エラー条件を適切に処理する必要がある場合です。(私アサートを使用しません)。例外については、私が知っている唯一のユースケースはctorが失敗する可能性がある場合で、他のすべてのケースは通常のエラー処理用です。
slashmais

5
@slashmais:感情は称賛に値しますが、完璧でバグのないコードを出荷しているのでなければ、未定義の動作よりもアサーション(ユーザーをクラッシュさせるものでも)の方が好ましいと思います。バグは複雑なシステムで発生し、アサーションを使用すると、バグが発生した場所を確認して診断することができます。
Kerrek SB 2016

@KerrekSBアサーションよりも例外を使用したいと思います。少なくとも、コードは失敗したブランチを破棄し、他の有用な何かをする機会があります。少なくとも、RAIIを使用している場合は、ファイルを開くためのすべてのバッファーが適切にフラッシュされます。
daemonspring 2017

14

アサーションは、メソッドの実行前または実行後などの内部状態などの内部実装の不変条件を検証するために使用できます。アサーションが失敗した場合は、プログラムのロジックが壊れていることを意味し、これから回復することはできません。この場合、できる限り最善の方法は、ユーザーに例外を渡さずにできるだけ早く中断することです。(少なくともLinuxでは)アサーションの本当に良い点は、プロセスの終了の結果としてコアダンプが生成されるため、スタックトレースと変数を簡単に調査できることです。これは、例外メッセージよりもロジック障害を理解するのに非常に役立ちます。


私も同様のアプローチをとっています。おそらくローカルで正しいはずのロジックにアサーションを使用します(例:ループ不変式)。例外は、非ローカル(外部)の状況によってコードに論理エラーが強制された場合です。
スプラフ2016

アサーションが失敗した場合、それはプログラムの一部のロジックが壊れていることを意味します。失敗したアサーションは、も達成できないことを必ずしも意味しません。壊れたプラグインはおそらくワードプロセッサ全体を中止すべきではありません。
daemonspring 2017

13

すべてのabort()が原因でデストラクタが実行されないことは未定義の動作ではありません!

もしそうなら、それも呼び出すのは未定義の振る舞いなstd::terminate()ので、それを提供することのポイントは何でしょうか?

assert() C ++でもCと同じように便利です。アサーションはエラー処理ではなく、プログラムを即座に中止するためのものです。


1
abort()プログラムをすぐに中止するためだと思います。ただし、アサーションはエラー処理用ではありませんが、アサートはアボートしてエラーを処理しようとします。代わりに例外をスローし、可能であれば呼び出し元にエラーを処理させませんか?結局のところ、呼び出し元は、ある関数の失敗によって他の機能を実行する価値がないかどうかを判断するのにより適切な位置にいます。たぶん、呼び出し側は3つの無関係なことをしようとしていて、それでも他の2つのジョブを完了して、このジョブを破棄することができます。
daemonspring 2017

またassert、呼び出すように定義されていますabort(条件がfalseの場合)。例外のスローに関しては、いいえ、それは必ずしも適切ではありません。発信者が処理できないものがあります。呼び出し元は、サードパーティライブラリ関数のロジックバグが回復可能かどうか、または破損したデータを修正できるかどうかを判断できません。
ジョナサンウェイクリー

6

私見、アサーションは、違反した場合に他のすべてをナンセンスにする条件をチェックするためのものです。したがって、それらから回復することはできません。回復することは重要ではありません。

私はそれらを2つのカテゴリにグループ化します。

  • 開発者の罪(たとえば、負の値を返す確率関数):

浮動確率(){-1.0を返す; }

assert(probability()> = 0.0)

  • マシンが壊れている(たとえば、プログラムを実行するマシンが非常に間違っている):

int x = 1;

assert(x> 0);

これらはどちらも些細な例ですが、現実からそれほど遠くないものです。たとえば、ベクトルで使用するために負のインデックスを返す単純なアルゴリズムについて考えます。またはカスタムハードウェアに埋め込まれたプログラム。またはむしろsh * tが起こるのでです。

そして、そのような開発ミスがあったとしても、実装されている回復メカニズムやエラー処理メカニズムについては確信を持てないはずです。同じことがハードウェアエラーにも当てはまります。


1
assert(probability()> = 0.0)
エリオット
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.