C++11 でのアサートと Constexpr

簡単な質問があります: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 関数、thrownoexcept あなたの友達です。または、 assert_failure を定義できます std::quick_exit のいずれかを使用して、プロセスを今すぐシャットダウンすることについて骨の折れる例外タイプ または、信頼できる古い assert で マクロ。

"\e"
"\e"