如何在不使用 abort() 的情况下断言()?
题
如果我使用 assert()
然后断言失败 assert()
将会通知 abort()
, ,突然结束正在运行的程序。在我的生产代码中我无法承受这一点。有没有一种方法可以在运行时断言,但又能够捕获失败的断言,以便我有机会优雅地处理它们?
解决方案
是的,事实上是有的。您需要自己编写一个自定义断言函数,如 C++ 的 assert()
正是 C 的 assert()
, ,与 abort()
“功能”捆绑在一起。幸运的是,这非常简单。
断言.hh
template <typename X, typename A>
inline void Assert(A assertion)
{
if( !assertion ) throw X();
}
如果谓词不成立,上面的函数将抛出异常。然后您将有机会捕获异常。如果你没有捕获异常, terminate()
将被调用,这将结束程序,类似于 abort()
.
您可能想知道当我们为生产构建时如何优化断言。在这种情况下,您可以定义常量来表示您正在为生产进行构建,然后在您构建时引用该常量。 Assert()
.
调试.hh
#ifdef NDEBUG
const bool CHECK_WRONG = false;
#else
const bool CHECK_WRONG = true;
#endif
主程序.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 节。
其他提示
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)
在此基础上,用 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))
我没有尝试编译它,但你明白它的意思。
笔记:您需要确保始终包含“异常”标头以及 boost 标头(如果您决定使用它来格式化错误消息)。但您也可以将“my_assert”设为函数并仅声明其原型。就像是:
void my_assert( const char* e, const char* file, int line);
并在可以自由包含所需的所有标头的地方实现它。
如果需要的话,将其包装在 #ifdef DEBUG 中;如果您总是想运行这些检查,则不要将其包装起来。
如果您想抛出一个包含有关断言信息的字符串:http://xll8.codeplex.com/SourceControl/latest#xll/ensure.h
_set_error_mode(_OUT_TO_MSGBOX);
相信我,这个功能可以帮助你。