を使用assert()
してアサーションが失敗すると、assert()
が呼び出さabort()
れ、実行中のプログラムが突然終了します。私の製品コードではそれを行う余裕はありません。実行時にアサートする方法はありますが、失敗したアサーションをキャッチして、それらを適切に処理する機会がありますか?
6 に答える
はい、実際にはあります。assert()
C++はまさに Cassert()
であり、abort()
「機能」がバンドルされているため、独自の assert 関数を自分で作成する必要があります。幸いなことに、これは驚くほど簡単です。
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()
s と同様に、アサーションも簡単に grep できます。
この手法の詳細については、Bjarne Stroustrup の The C++ Programming Language 3e のセクション 24.3.7.2 を参照してください。
glib のエラー報告関数は、アサート後に継続するというアプローチを取ります。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);
}
C/C++ のアサートは、デバッグ ビルドでのみ実行されます。したがって、これは実行時に発生しません。一般に、アサートは、発生した場合にバグを示すものをマークし、通常はコード内の仮定を示す必要があります。
実行時 (リリース時) にエラーをチェックするコードが必要な場合は、アサートではなく例外を使用する必要があります。あなたの答えは基本的に、アサート構文で例外スローワーをラップします。これは機能しますが、最初に例外をスローすることよりも特に利点はありません。
これが私が「assert.h」(Mac 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(exception)に置き換えます。また、printfの代わりに、文字列を例外のエラーメッセージにフォーマットすることができます。最終的に、次のようなものが得られます。
#define assert(e) ((void) ((e) ? 0 : my_assert (#e, __FILE__, __LINE__)))
#define my_assert( e, file, line ) ( throw std::runtime_error(\
std::string(file:)+boost::lexical_cast<std::string>(line)+": failed assertion "+e))
私はそれをコンパイルしようとはしていませんが、あなたは意味を理解しています。
注:「例外」ヘッダーとブースト(エラーメッセージのフォーマットに使用する場合)が常に含まれていることを確認する必要があります。ただし、「my_assert」を関数にして、そのプロトタイプのみを宣言することもできます。何かのようなもの:
void my_assert( const char* e, const char* file, int line);
そして、必要なすべてのヘッダーを自由に含めることができる場所に実装します。
必要な場合は#ifdefDEBUGでラップし、常にこれらのチェックを実行したい場合はラップしません。
アサーションに関する情報を含む文字列をスローする場合: http://xll8.codeplex.com/SourceControl/latest#xll/ensure.h