如何在不使用abort()的情况下断言()?
如果我使用assert()
并且断言失败,则assert()
将调用abort()
,从而突然终止正在运行的程序。我在生产代码中买不起。有没有一种方法可以在运行时进行断言,但是能够捕获失败的断言,因此我有机会优雅地处理它们?
解决方案
回答
是的,事实上是这样。我们将需要自己编写一个自定义的断言函数,因为C ++的assert()
恰好是C的assert()
,并且捆绑了abort()
" feature"。幸运的是,这非常简单。
断言
template <typename X, typename A> inline void Assert(A assertion) { if( !assertion ) throw X(); }
如果谓词不成立,则上述函数将引发异常。然后,我们将有机会捕获异常。如果我们没有捕获到异常,将调用terminate()
,这将类似于abort()
结束程序。
我们可能会想知道,当我们为生产而构建时,如何优化断言。在这种情况下,我们可以定义常量以表示我们正在为生产而构建,然后在我们使用Assert()时引用该常量。
调试
#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节。
回答
C / C ++中的断言仅在调试版本中运行。因此,这不会在运行时发生。一般而言,断言应标记事物,如果它们发生则表明存在错误,并通常在代码等中显示假设。
如果我们想拥有在运行时(发行版中)检查错误的代码,则可能应使用异常而不是断言,因为它们是设计用来执行的。答案基本上是使用断言语法包装异常抛出器。尽管这将起作用,但与首先抛出异常相比,我没有看到任何特别的好处。
回答
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); }
回答
这是我在" 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(exception)替换对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中,如果我们始终希望运行这些检查,则可以不使用。