web-dev-qa-db-ja.com

C ++でassert()を使用するのは悪い習慣ですか?

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

一方、C++は_std::logic_error_を定義します。これは、プログラムのロジックにエラーがある場合(名前の由来)にスローされることを意図しています。インスタンスをスローすることは、assertに代わる、完全なC++風の代替案にすぎないかもしれません。

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

この問題に関する3つの意見を考えることができます:

  • Cのアサートにこだわる。プログラムはすぐに終了するため、変更が正しく展開されたかどうかは関係ありません。また、C++で_#define_ sを使用することも同様に悪いことです。
  • 例外をスローし、main()でキャッチします。プログラムの任意の状態でデストラクタをスキップするコードを許可することは悪い習慣であり、すべてのコストで回避する必要があります。したがって、terminate()の呼び出しも同様です。例外がスローされた場合、それらをキャッチする必要があります。
  • 例外をスローし、プログラムを終了させます。プログラムを終了する例外は問題ありません。NDEBUGが原因で、これは起こりません。リリースビルドで。キャッチは不要で、内部コードの実装の詳細をmain()に公開します。

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

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

80
Fabian Knorr

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

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

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


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

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

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

  • 例外は例外的な状況です。遭遇した場合、ユーザーは自分が望むことをすることはできませんが、別の場所から再開することができます。

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

88
Kerrek SB

すべてのabort()によるデストラクタの実行は未定義の動作ではありません!

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

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

12
Jonathan Wakely

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

12
nogard

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

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

  • 開発者の罪(負の値を返す確率関数など):

float potential(){return -1.0; }

assert(probability()> 0.0)

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

int x = 1;

assert(x> 0);

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

そして、そのような開発の間違いがある場合、実装された回復またはエラー処理メカニズムについて自信を持ってはいけません。同じことがハードウェアエラーにも当てはまります。

4
FranMowinckel