[英]Is using assert() in C++ bad practice?
我倾向于在我的 C++ 代码中添加大量断言,以便在不影响发布版本性能的情况下更容易调试。 现在, assert
是一个纯 C 宏,设计时没有考虑 C++ 机制。
另一方面,C++ 定义了std::logic_error
,它意味着在程序逻辑中存在错误的情况下抛出(因此得名)。 抛出一个实例可能只是assert
的完美的、更 C++ 风格的替代方案。
问题是assert
和abort
都立即终止程序而不调用析构函数,因此跳过清理,而手动抛出异常会增加不必要的运行时成本。 解决这个问题的一种方法是创建一个自己的断言宏SAFE_ASSERT
,它的工作方式与 C 对应物一样,但在失败时抛出异常。
对于这个问题,我能想到三个意见:
#define
也同样糟糕。NDEBUG
,这在发布版本中永远不会发生。 捕获是不必要的,并将内部代码的实现细节暴露给main()
。这个问题有明确的答案吗? 有专业参考吗?
编辑:当然,跳过析构函数不是未定义的行为。
断言用于调试。 您所提供代码的用户不应看到它们。 如果断言被命中,你的代码需要被修复。
该产品包含可以被攻击者触发的 assert() 或类似语句,这会导致应用程序退出或其他比必要更严重的行为。
虽然断言有助于捕获逻辑错误并减少达到更严重漏洞情况的机会,但它仍然可能导致拒绝服务。
例如,如果服务器同时处理多个连接,并且在一个连接中发生 assert() 导致所有其他连接被丢弃,则这是导致拒绝服务的可达断言。
例外是针对特殊情况的。 如果遇到,用户将无法做她想做的事,但可以在其他地方继续。
错误处理适用于正常的程序流程。 例如,如果您提示用户输入一个数字并得到一些无法解析的信息,这是正常的,因为用户输入不受您的控制,您必须始终按照理所当然地处理所有可能的情况。 (例如循环直到你有一个有效的输入,中间说“对不起,再试一次”。)
断言完全适用于 C++ 代码。 异常和其他错误处理机制并不是真正用于与断言相同的事情。
错误处理适用于有可能向用户很好地恢复或报告错误的情况。 例如,如果尝试读取输入文件时出现错误,您可能需要对此做一些事情。 错误可能由错误导致,但它们也可能只是给定输入的适当输出。
断言用于诸如在通常不会检查 API 时检查 API 要求是否得到满足的事情,或者检查开发人员认为他通过构造保证的事情。 例如,如果算法需要排序输入,您通常不会检查它,但您可能有一个断言来检查它,以便调试构建标记那种错误。 断言应始终表明程序运行不正确。
如果您正在编写一个程序,其中不正常关闭可能会导致问题,那么您可能希望避免断言。 严格按照 C++ 语言的未定义行为在这里不属于此类问题,因为命中断言可能已经是未定义行为的结果,或者违反了一些其他要求,这可能会阻止某些清理工作正常进行。
此外,如果您根据异常实现断言,那么它可能会被捕获并“处理”,即使这与断言的目的相矛盾。
断言可用于验证内部实现不变量,例如某些方法执行之前或之后的内部状态等。如果断言失败,则确实意味着程序逻辑已损坏,您无法从中恢复。 在这种情况下,您能做的最好的事情就是尽快中断而不将异常传递给用户。 断言的真正好处(至少在 Linux 上)是核心转储是作为进程终止的结果生成的,因此您可以轻松地调查堆栈跟踪和变量。 这对于理解逻辑失败比异常消息更有用。
由于 alling abort() 而没有运行析构函数并不是未定义的行为!
如果是,那么调用std::terminate()
也是未定义的行为,那么提供它有什么意义呢?
assert()
在 C++ 中和在 C 中一样有用。断言不是用于错误处理,而是用于立即中止程序。
恕我直言,断言是为了检查条件,如果被违反,其他一切都是废话。 因此,您无法从它们中恢复,或者更确切地说,恢复是无关紧要的。
我将它们分为两类:
浮动概率(){返回-1.0; }
断言(概率()> = 0.0)
整数 x = 1;
断言(x > 0);
这些都是微不足道的例子,但与现实相差不远。 例如,考虑返回负索引以用于向量的朴素算法。 或定制硬件中的嵌入式程序。 或者更确切地说,因为sh*t 发生了。
如果存在此类开发错误,您不应该对实施的任何恢复或错误处理机制充满信心。 这同样适用于硬件错误。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.