2008-09-01 5 views
23

assert()を使用してアサーションに失敗すると、assert()abort()を呼び出し、実行中のプログラムを突然終了させます。私は自分の生産コードでそれを買う余裕がありません。実行時にアサートする方法がありますが、失敗したアサーションをキャッチして正常に処理する機会がありますか?abort()を使用せずにどのようにアサート()することができますか?

答えて

23

はい、実際は問題ありません。 C++のassert()は正確にCのassert()であり、abort()の "feature"がバンドルされているので、自分自身でカスタムのアサーション関数を書く必要があります。幸いにも、これは驚くほど簡単です。

Assert.hh

template <typename X, typename A> 
inline void Assert(A assertion) 
{ 
    if(!assertion) throw X(); 
} 

述語が成立しない場合は、上記の関数が例外をスローします。その後、例外をキャッチするチャンスがあります。例外をキャッチしない場合はterminate()が呼び出され、abort()と同様にプログラムが終了します。

生産用にアサーションを最適化する方法については、何か疑問に思うかもしれません。この場合、生産のためにビルドしていることを示す定数を定義し、次にAssert()という定数を参照することができます。

debug.hh

#ifdef NDEBUG 
    const bool CHECK_WRONG = false; 
#else 
    const bool CHECK_WRONG = true; 
#endif 

main.cc

#include<iostream> 

struct Wrong { }; 

int main() 
{ 
    try { 
     Assert<Wrong>(!CHECK_WRONG || 2 + 2 == 5); 
     std::cout << "I can go to sleep now.\n"; 
    } 
    catch(Wrong e) { 
     std::cerr << "Someone is wrong on the internet!\n"; 
    } 

    return 0; 
} 

CHECK_WRONGが一定であれば、Assert()への呼び出しは、アサーションがある場合でも、生産に離れてコンパイルされます定数式ではありません。 CHECK_WRONGを参照するともう少しタイプするという点で少し不都合があります。しかし、交換では、さまざまなグループのアサーションを分類し、それぞれのアサーションを有効または無効にできるという利点があります。たとえば、本番用コードでも有効にしたいアサーションのグループを定義し、開発ビルドにしか見せたくないアサーションのグループを定義することができます。

Assert()機能は

if(!assertion) throw X(); 

をタイプと同等ですが、それは明らかに、プログラマの意図を示しています。アサーションを作ります。アサーションは、普通のassert()のように、このアプローチではgrepの方が簡単です。

このテクニックの詳細については、Bjarne Stroustrupの「C++プログラミング言語3e」のセクション24.3.7.2を参照してください。

+0

あなただけの今までデフォルト構築の例外を取得することができますので、あなたが任意の実行時データを転送することができないので、これまで多くの利点がありませんキャッチサイト... –

+0

assert引数をintにするのではなく、templatizingの付加価値は何ですか?最終的にはintに変換する必要があります。 – JohnMcG

+0

@グレッグロジャース ビャーネ・ストロヴストルップは、あなたが言及問題へのソリューションを提供しています:あなたは)あなたがアサート(の追加のパラメータとしてスローされたいカスタマイズされたオブジェクトを追加することができます。欠点は、余分なパラメータのためにAssert()がassert()のように見えなくなることです。 – wilhelmtell

6

C/C++でのアサートは、デバッグビルドでのみ実行されます。これは実行時には発生しません。一般的に、アサーションはバグを示す場合には、バグを示すものをマークし、コードなどに一般的に仮定を表示する必要があります。

実行時(リリース時)にエラーをチェックするコードを使用する場合は、これらは、彼らがするように設計されていると主張する。あなたの答えは、基本的にasser構文で例外スローダをラップします。これはうまくいくが、これに特別なメリットはない。なぜなら、最初に例外を投げることだけを見ることができるからである。

+0

プロダクションビルドで実行中または実行中でないアサーションは、コンパイラオプションです。それらは、最適化オプションに関係なく、gccでデフォルトで実行されます。 –

+2

私は個人的にリリースモードでアサートを維持することをお勧めします。あなたのクライアントがアプリケーションを使用するときにランダムクラッシュすることを好むかもしれません... –

+2

特に、そのアサーションが偽になるとプログラムが未定義の動作につながる場合、クライアントは、少なくともより価値のあるエラーメッセージと簡単な再生ステップを提供することができます。それが最初に起こることを決して意味しなかったと考えて、あなたはそれがないと思っていた(保証されていた)。 – dcousens

10

glib's error reporting functionsアサート後も引き続きアプローチします。 glibはGnome(GTK経由)が使用する基盤となるプラットフォーム独立ライブラリです。前提条件をチェックし、前提条件が失敗した場合にスタックトレースを出力するマクロがあります。

#define RETURN_IF_FAIL(expr)  do {     \ 
if (!(expr))           \ 
{              \ 
     fprintf(stderr,        \ 
       "file %s: line %d (%s): precondition `%s' failed.", \ 
       __FILE__,           \ 
       __LINE__,           \ 
       __PRETTY_FUNCTION__,        \ 
       #expr);            \ 
     print_stack_trace(2);          \ 
     return;             \ 
};    } while(0) 
#define RETURN_VAL_IF_FAIL(expr, val) do {       \ 
if (!(expr))              \ 
{                 \ 
     fprintf(stderr,            \ 
       "file %s: line %d (%s): precondition `%s' failed.",  \ 
       __FILE__,            \ 
       __LINE__,            \ 
       __PRETTY_FUNCTION__,         \ 
       #expr);             \ 
     print_stack_trace(2);           \ 
     return val;             \ 
};    } while(0) 

ここでは、GNUツールチェーン(GCC)を使用する環境のために書かれ、スタックトレースを表示する関数です:

void print_stack_trace(int fd) 
{ 
    void *array[256]; 
    size_t size; 

    size = backtrace (array, 256); 
    backtrace_symbols_fd(array, size, fd); 
} 

これは、マクロを使用したい方法です:

char *doSomething(char *ptr) 
{ 
    RETURN_VAL_IF_FAIL(ptr != NULL, NULL); // same as assert(ptr != NULL), but returns NULL if it fails. 

    if(ptr != NULL)  // Necessary if you want to define the macro only for debug builds 
    { 
     ... 
    } 

    return ptr; 
} 

void doSomethingElse(char *ptr) 
{ 
    RETURN_IF_FAIL(ptr != NULL); 
} 
+3

@wilhelmtell I私は例外を使用することに同意しないので、これが今までの最良の解決策であると考えてください。例外は例外的なものでなければなりませんが、Assertの処理は例外ではありません。また、パフォーマンスが重要でない限り、リリースビルドにAsserts Onを残しておく予定がある場合は、この方法が効率的になります。あなたはそれをパーソナライズすることもできますので、プログラムが終了しても必要なときに終了する必要はなく、情報を元に戻すことができます。アサーションと例外に関するThe Pragmatic Programmerの本の章を読むことをお勧めします。プラス[このリンク](http://www.gotw.ca/publications/mill22.htm)。 – ForceMagic

5

は、ここで私は私の中に "assert.h"(マックOS 10.4)を持っているものです。

#define assert(e) ((void) ((e) ? 0 : __assert (#e, __FILE__, __LINE__))) 
#define __assert(e, file, line) ((void)printf ("%s:%u: failed assertion `%s'\n", file, line, e), abort(), 0) 

それに基づいて、abort()の呼び出しをthrow(例外)に置き換えます。 printfの代わりに、文字列を例外のエラーメッセージに書式設定することができます。結局、次のようなものが得られます:

私はそれをコンパイルしようとしませんでしたが、あなたは意味を持っています。

注:エラーメッセージのフォーマットに使用する場合は、「例外」ヘッダーが常に含まれていることと、ブーストが含まれていることを確認する必要があります。しかし、 "my_assert"を関数にして、そのプロトタイプだけを宣言することもできます。あなたは自由にあなたが必要とするすべてのヘッダーを含めることができ

void my_assert(const char* e, const char* file, int line); 

そして、どこかでそれを実装:ような何か。

必要に応じていくつかの#ifdef DEBUGにラップしてください。そうしたチェックを常に実行したい場合は、そうではありません。

+0

したがって、マクロがテンプレート関数よりも優れているかどうかは議論の余地があります。 また、シーケンス演算子のオペランドは任意の順序で実行できます。これが__asert()で何を意味するのか疑問です。 – wilhelmtell

+0

最初のブロックの "__assert"機能は、私のシステムヘッダ(0S 10.4)から来ています。マクロを使用すると、FILEおよびLINE情報を簡単に取得できます。しかし、いくつかの点では、それは非常に化粧品になります... – rlerallut

-1
_set_error_mode(_OUT_TO_MSGBOX); 

この機能は私を信じてくれます。

+0

これはWindowsのみの機能です:[リンク](https://msdn.microsoft.com/en-us/library/aa235388(v=60))。 aspx) –

+0

そして少なくともあなたはそれを使用するために必要なヘッダ/コンパイラ/何を説明しておかなければなりません。 –

関連する問題