web-dev-qa-db-ja.com

カスタムC ++アサートマクロ

私は有益な記事を偶然見つけました: http://cnicholson.net/2009/02/stupid-c-tr​​icks-adventures-in-assert/ これは、私の中に存在する多くの問題を指摘しました現在のデバッグマクロスイート。

リンクをクリックすると、記事の最後近くにマクロの最終バージョンの完全なコードが表示されます。

提示された一般的な形式は次のとおりです(転置が間違っている場合は誰かが私を修正してください):

_#ifdef DEBUG
#define ASSERT(cond) \  
    do \  
    { \  
        if (!(cond)) \  
        { \  
            ReportFailure(#cond, __FILE__, __LINE__, 0); \
            HALT(); \
        } \  
    } while(0)  
#else  
#define ASSERT(cond) \  
    do { (void)sizeof(cond); } while(0) 
_

私が学んだことでコードを変更することを考えているときに、その記事のコメントに投稿されたいくつかの興味深いバリエーションに気づきました。

1つは、このマクロを三項演算子(つまりcond?ASSERT(x):func())と一緒に使用できないことで、if()を三項演算子といくつかの括弧、およびコンマ演算子で置き換えることが提案されました。後で別のコメンターがこれを提供しました:

_#ifdef DEBUG
#define ASSERT(x) ((void)(!(x) && assert_handler(#x, __FILE__, __LINE__) && (HALT(), 1)))
#else
#define ASSERT(x) ((void)sizeof(x))
#endif
_

この場合、論理および_&&_の使用は特にスマートであると思いました。このバージョンは、ifまたは3項_?:_を使用するバージョンよりも柔軟性があるようです。さらに良いのは、_assert_handler_の戻り値を使用して、プログラムを停止する必要があるかどうかを判断できることです。ただHALT()ではなく_(HALT(), 1)_である理由はわかりませんが。

ここで私が見落としている2番目のバージョンの特定の欠点はありますか?マクロを囲むdo{ } while(0)は不要ですが、ifsを処理する必要がないため、ここでは不要であるようです。

どう思いますか?

28
Steven Lu

CおよびC++標準ライブラリでは、assertは関数として機能するために必要なマクロです。その要件の一部は、ユーザーがexpressionsで使用できる必要があることです。たとえば、標準のassertを使用すると、

_int sum = (assert(a > 0), a) + (assert(b < 0), b);
_

機能的には同じです

_assert(a > 0 && b < 0)
int sum = a + b;
_

前者は式を書くための非常に良い方法ではないかもしれませんが、トリックは多くのより適切なケースで依然として非常に役立ちます。

つまり、独自のカスタムASSERTマクロが標準のassertの動作と使いやすさを模倣したい場合、ifまたはdo { } while (0)ASSERTは問題外です。その場合、式は_?:_演算子または短絡論理演算子のいずれかを使用することを意味します。

もちろん、標準のようなカスタムASSERTを作成する必要がない場合は、ifを含め、何でも使用できます。リンクされた記事はこの問題を考慮していないようですが、これはかなり奇妙です。私の意見では、関数のようなアサートマクロは、関数のようなアサートマクロよりも明らかに便利です。

_(HALT(), 1)_演算子は有効な引数を必要とするため、_&&_...と同様に行われます。 HALT()の戻り値は、_&&_の有効な引数を表していない可能性があります。私が知っていることはvoidになる可能性があります。つまり、単なるHALT()は、単に_&&_の引数としてコンパイルされません。 _(HALT(), 1)_は常に_1_に評価され、タイプintを持ちます。これは常に_&&_の有効な引数です。したがって、HALT()のタイプに関係なく、_(HALT(), 1)_は常に_&&_の有効な引数です。

do{ } while(0)に関する最後のコメントはあまり意味がありません。マクロをdo{ } while(0)で囲む目的は、マクロ定義内のifsではなく、外部ifsを処理することです。 alwaysマクロは外部ifで使用される可能性が常にあるため、外部ifsを処理する必要があります。後者の定義では、そのマクロはであるため、do{ } while(0)は必要ありません。そして、式であるため、外部のifsには既に問題はありません。したがって、それらについて何もする必要はありません。さらに、上で述べたように、それをdo{ } while(0)で囲むと、その目的が完全に無効になり、非式になります。

28
AnT

完全を期すために、C++でのドロップイン2ファイルアサートマクロ実装を公開しました。

_#include <pempek_assert.h>

int main()
{
  float min = 0.0f;
  float max = 1.0f;
  float v = 2.0f;
  PEMPEK_ASSERT(v > min && v < max,
                "invalid value: %f, must be between %f and %f", v, min, max);

  return 0;
}
_

プロンプトが表示されます:

_Assertion 'v > min && v < max' failed (DEBUG)
  in file e.cpp, line 8
  function: int main()
  with message: invalid value: 2.000000, must be between 0.000000 and 1.000000

Press (I)gnore / Ignore (F)orever / Ignore (A)ll / (D)ebug / A(b)ort:
_

どこ

  • (I)無視:現在のアサーションを無視します
  • 無視(F)またはすべて:アサーションが発生したファイルと行を覚え、プログラムの残りの実行では無視します。
  • (A)llを無視:残りのすべてのアサーション(すべてのファイルと行)を無視します
  • (D)ebug:アタッチされている場合はデバッガーに侵入し、そうでない場合はabort()(Windowsでは、システムはユーザーにデバッガーをアタッチするように要求します)
  • A(b)ort:すぐにabort()を呼び出します

あなたはそこでそれについてもっと知ることができます:

お役に立てば幸いです。

11
Gregory Pakosz

HALT()ではなく(HALT(), 1)である理由はわかりませんが。

HALTexitのマクロ(または他の代替名)かもしれません。 HALTコマンドにexit(1)を使用したいとします。 exitvoidを返します。これは、&&の2番目の引数として評価できません。最初の引数を評価し、次に2番目の引数の値を評価して返すコンマ演算子を使用すると、HALT()のためにそのポイントに到達しなかったとしても、整数(1)で&&に戻ります。それまでずっと停止します。

基本的に、HALTに値を入力する関数は、おそらくvoidの戻り値を持ちます。値を返すのは意味がないからです。我々はcouldマクロのためだけにintを返すようにしますが、すでにマクロでハッキングしている場合、もう少しハッカーが害を及ぼすことはありませんが、 ?

6
Chris Lutz