当前位置:  开发笔记 > 编程语言 > 正文

如何在不使用abort()的情况下断言()?

如何解决《如何在不使用abort()的情况下断言()?》经验,为你挑选了4个好方法。

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



1> wilhelmtell..:

是的,事实确实如此.你需要自己编写一个自定义断言函数,因为C++ assert()是C语言assert(),abort()捆绑了"特性".幸运的是,这非常简单.

Assert.hh

template 
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

struct Wrong { };

int main()
{
    try {
        Assert(!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的The C++ Programming Language 3e,第24.3.7.2节.



2> indiv..:

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);
}


@wilhelmtell我相信这是迄今为止最好的解决方案,因为我不同意使用异常.异常应该是特殊的东西,但处理断言不是.此外,如果您计划在发布版本中保留Asserts On,我认为除非性能至关重要,否则此方法将更有效.您还可以对其进行个性化设置,以便在程序发生时无需退出,您将获得相关信息.我建议阅读The Pragmatic Programmer一书中关于断言和例外的章节.另外[此链接](http://www.gotw.ca/publications/mill22.htm).

3> Ben Childs..:

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

如果您希望在运行时(在发行版中)拥有检查错误的代码,您应该使用异常而不是断言,因为这些是他们的目的.你的答案基本上包含了assert语法中的异常thrower.虽然这样可行,但没有特别的优势,我可以看到只是抛出异常.


我个人认为将断言保持在发布模式是个好主意.当您的客户使用应用程序时,您可能更喜欢随机崩溃...
特别是如果该断言变为假,您的程序将导致未定义的行为; 它将允许客户至少提供更有价值的错误信息和更容易的复制步骤; 考虑到它从来没有意味着首先发生; 而你认为(并保证)它不会.

4> rlerallut..:

以下是我在"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(line)+": failed assertion "+e))

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

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

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

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

如果需要,请将其包含在某些#ifdef DEBUG中,如果您总是想要运行这些检查,请将其包装.

推荐阅读
U友50081205_653
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有