27

如果我使用assert()并且断言失败,那么assert()将调用abort(),突然结束正在运行的程序。在我的生产代码中我负担不起。有没有办法在运行时断言但能够捕获失败的断言,以便我有机会优雅地处理它们?

4

6 回答 6

28

是的,事实上是有的。您将需要自己编写一个自定义断言函数,因为 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

主文件

#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();

但它清楚地表明了程序员的意图:做出断言。使用这种方法,断言也更容易 grep,就像 plain assert()s 一样。

有关此技术的更多详细信息,请参阅 Bjarne Stroustrup 的 C++ 编程语言 3e,第 24.3.7.2 节。

于 2008-09-01T05:15:18.800 回答
11

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);
}
于 2008-10-03T19:53:30.633 回答
6

C/C++ 中的断言仅在调试版本中运行。所以这不会在运行时发生。一般来说,断言应该标记一些事情,如果它们发生则表明一个错误,并且通常在你的代码等中显示假设。

如果您想要在运行时(发布时)检查错误的代码,您可能应该使用异常而不是断言,因为这些是它们的设计目的。您的答案基本上用断言语法包装了一个异常抛出器。虽然这会起作用,但与一开始就抛出异常相比,我认为这并没有什么特别的优势。

于 2008-09-01T05:27:55.760 回答
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)

基于此,将调用 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))

我没有尝试编译它,但你明白了。

注意:您需要确保始终包含“异常”标头以及 boost(如果您决定使用它来格式化错误消息)。但是你也可以让“my_assert”成为一个函数并且只声明它的原型。就像是:

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

并在您可以自由包含所需的所有标题的地方实现它。

如果您需要它,或者如果您总是想运行这些检查,则将其包装在一些#ifdef DEBUG 中。

于 2008-11-06T23:40:02.633 回答
0

如果你想抛出一个包含断言信息的字符串:http: //xll8.codeplex.com/SourceControl/latest#xll/ensure.h

于 2016-02-06T12:58:24.243 回答
-2
_set_error_mode(_OUT_TO_MSGBOX);

相信我,这个功能可以帮助你。

于 2014-10-08T09:31:52.627 回答