如果我使用assert()
并且断言失败,则assert()
将调用abort()
,突然结束正在运行的程序。我的生产代码中负担不起。有没有办法在运行时断言但能够捕获失败的断言,以便我有机会优雅地处理它们?
答案 0 :(得分:24)
是的,事实上是有的。您需要自己编写自定义断言函数,因为C ++的assert()
正好是C assert()
,其中捆绑了abort()
“特征。幸运的是,这非常简单。
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。
有关此技术的更多详细信息,请参阅Bjarne Stroustrup的“C ++编程语言3e”,第24.3.7.2节。
答案 1 :(得分:11)
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);
}
答案 2 :(得分:6)
C / C ++中的断言仅在调试版本中运行。所以这不会在运行时发生。一般来说,断言应该标记如果它们发生的事情表明存在错误,并且通常在代码中显示假设等。
如果你想拥有在运行时(在发布版本中)检查错误的代码,你应该使用异常而不是断言,因为这些是他们的目的。你的答案基本上包含了assert语法中的异常thrower。虽然这样可行,但没有特别的优势,我可以看到首先抛出异常。
答案 3 :(得分:5)
这就是我在“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)
基于此,用throw(异常)替换对abort()的调用。而不是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))
我没有尝试编译它,但你明白了。
注意:您需要确保始终包含“exception”标头,以及boost(如果您决定使用它来格式化错误消息)。但是你也可以使“my_assert”成为一个函数并且只声明它的原型。类似的东西:
void my_assert( const char* e, const char* file, int line);
并在可以自由包含所需标题的地方实施。
如果需要,请将其包含在某些#ifdef DEBUG中,如果您总是想要运行这些检查,请将其包裹。
答案 4 :(得分:0)
如果要抛出包含断言信息的字符串: http://xll8.codeplex.com/SourceControl/latest#xll/ensure.h
答案 5 :(得分:-2)
_set_error_mode(_OUT_TO_MSGBOX);
相信我,这个功能可以帮到你。