如何做出稳健的断言

How to make robust assert?

本文关键字:断言 何做出      更新时间:2023-10-16

我想实现这种行为:

  • 当程序在调试模式下运行时,assert_robust(expression, commands)严格按照经典assert(expression)工作
  • 当程序在发布模式下运行时,如果表达式为 false,assert_robust(expression, commands)执行一些commands

这可以通过以下方式完成:

#ifdef NDEBUG
    #define assert_robust(expression, command) if (!(expression)) command;
#else
    #define assert_robust(expression, command) assert(expression); 
#endif
例如,

这可以用于使myfunction容错

char myfunction(const string& s, int i)
{
    assert_robust(i >= 0, return '');
    /* Normal code */
}

这很好用,但是如何制作支持多个(任意)数量的命令的宏assert_robust呢?(最好通过标准的C++方式)

另一个问题是:

调试严格,发布仁慈是好事吗?

编辑:我的想法为什么要做这样的事情是因为如果它是程序中的错误,程序维护有时比崩溃和用户丢失数据时有点奇怪,那么实用性要好得多。

更有趣的问题是第二个:

调试严格,发布仁慈是好事吗?

我的经验是,在调试和发布版本中具有不同的行为是一个可怕的想法。您正在注册生产中的问题,您将永远无法在调试版本中重现这些问题,因为两者的行为不同。

除此之外,如果您首先在调试模式下断言,您可能会声称这不会成为问题,断言应该用于标记编程问题,您无法安全恢复的情况。如果可以在发布模式下安全恢复,为什么要在 DEBUG 中断言?如果你不能,你是否愿意以一种你不太了解它能做什么的方式摆弄生产数据?

在不讨论这是否是一个好主意的情况下,您可以使用宏将多个命令包装在do-while(0);循环中。

#ifdef NDEBUG
    #define assert_robust(expression, command) if (!(expression)) 
                                                 do{command;} while(0)
#else
    #define assert_robust(expression, command) assert(expression)
#endif

另请注意,我没有在宏的末尾包含分号。如果您将它们包含在宏中,则类似

assert_robust(cond1, command1) /* no semicolon here, no problem */
assert_robust(cond2, command2) /* no semicolon here, no problem */

会被允许,这真的很奇怪。

我认为

以这种方式使用断言不是一个好主意。通常,如果您希望谓词始终为真,则可以使用断言,因为它是关键代码的一部分。如果不是真的,那么显然存在一个大问题,中止是合理的。但是越来越多的人像普通的错误检查一样使用断言进行调试。在这种情况下,在发布模式下完全禁用它就足够了。它认为您应该决定采用这两种方法之一。

但是,如果您想在中止之前运行某种紧急命令,则可以使用 C++11 中的新 lambda 函数:

void ASSERT(int expression, std::function<void()> func) {
    if(!expression) {
        if (func) func();
        abort();
    }
}

你可以像这样使用它:

ASSERT(a >= 0, []() { std::cerr << "ERROR" << std::endl;});

或:

ASSERT(a >= 0, [this]() { this->terminate(); });

或:

ASSERT(a >= 0, nullptr);