簡単な質問があります:C++11 で constexpr
にデバッグ チェックを配置する最良の方法は何ですか? 機能? assert
以降 constexpr
ではありません 、明らかなことは機能しません:
constexpr bool in_range(int val, int min, int max) { assert(min <= max); // OOPS, not constexpr return min <= val && val <= max; }
C++14 ではこれで問題ありませんが、C++11 に移植できる必要があるコードではそうではありません。これは既知の問題であり、推奨される解決策は throw
を使用することです 失敗時の表現。この完全に非自明な解決策には、コンパイル時に引数がわかっている場合にコンパイル時エラーを引き起こすという優れた効果があります。
constexpr bool in_range(int val, int min, int max) { return (min <= max) ? min <= val && val <= max : throw std::logic_error("Assertion failed!"); } constexpr bool res1 = in_range(4, 1, 5); // OK constexpr bool res2 = in_range(4, 5, 1); // Compile error! int min = 1, max = 5; bool b = in_range( 4, max, min ); // Exception!
1 つの点を除いては問題ありません:ランタイム アサーションを有効にします — すべき 回復不能になる — 「回復可能な」実行時例外に。それは本当に、本当に悪いです。アサーションは、プログラムの論理エラーを検出するために使用されます。 1 つが発生した場合は、プログラムの状態が疑わしいことを意味します。無効なプログラム状態から安全に回復する方法はないため、例外はジョブにとって不適切なツールです。
いくつかの解決策を見てみましょう:
修正 #1:noexcept
1 つの修正は非常に簡単です:noexcept
を追加します。 constexpr
に 関数:
constexpr bool in_range(int val, int min, int max) noexcept { return (min <= max) ? min <= val && val <= max : throw std::logic_error("Assertion failed!"); } int min = 1, max = 5; bool b = in_range( 4, max, min ); // Terminate!
in_range
に注意してください noexcept
と宣言されています —しかし、それはエラーをスローします!伝播中の例外が noexcept
にヒットするとどうなるか ?ゲームオーバーです、男。ランタイムは std::terminate
を呼び出します 、プロセスをシャットダウンします。それが assert
です 想定
修正 #2:std::quick_exit
これは別の簡単な修正です:assert_failure
を定義することができます コンストラクタでプロセスをシャットダウンする例外タイプ:
struct assert_failure { explicit assert_failure(const char *sz) { std::fprintf(stderr, "Assertion failure: %s\n", sz); std::quick_exit(EXIT_FAILURE); } };
これで assert_failure
を使用できます constexpr
で 次のようにバグをキャッチする関数:
constexpr bool in_range(int val, int min, int max) { return (min <= max) ? min <= val && val <= max : throw assert_failure("min > max!"); }
assert_failure
が コンストラクターはエラーを報告し、std::quick_exit
を呼び出します . quick_exit
は C++11 の新しい関数で、ローカルまたはグローバル オブジェクトのデストラクタを呼び出さずにプロセスをシャットダウンするだけです。それはほぼ確実にあなたが望むものです。プログラムの状態が中断している場合、任意のコードの山を実行するのは悪い考えです。それは良いことよりも害を及ぼす可能性があります。絶対にしなければならないすべてのコード std::at_quick_exit
に何を登録する必要があるかに関係なく、終了時に実行します .後で回復を試みるために、ユーザーの編集をルックアサイドに保存するなどのことに制限する必要があります。 (ただし、既知の正常なデータを破損しないでください!)
修正 #3:アサート
修正 #2 の問題点は、assert
について知っているデバッガーに干渉することです。 そして、発火したときに何をすべきか。 3 番目の修正は、assert
を使用することです。 、しかし卑劣な方法でそれを行うこと。もう一度、 assert_failure
を定義します 型ですが、今回は assert
を実行する関数をコンストラクターに渡します 私たちのために:
struct assert_failure { template<typename Fun> explicit assert_failure(Fun fun) { fun(); // For good measure: std::quick_exit(EXIT_FAILURE); } }; constexpr bool in_range(int val, int min, int max) { return (min <= max) ? min <= val && val <= max : throw assert_failure( []{assert(!"input not in range");} ); }
実行時エラーが発生すると、assert
が返されます そもそも私たちが本当に欲しかったもの:
assertion "!"input not in range"" failed: file "main.cpp", line 41, function: auto in_range(int, int, int)::(anonymou s class)::operator()() const Aborted (core dumped)
まとめ
デバッグ チェックを追加する場合は、C++11 constexpr
関数、throw
と noexcept
あなたの友達です。または、 assert_failure
を定義できます std::quick_exit
のいずれかを使用して、プロセスを今すぐシャットダウンすることについて骨の折れる例外タイプ または、信頼できる古い assert
で マクロ。
"\e"
"\e"