bec*_*cko 5 c++ assert exception
之前已经在这里询问过异常vs assert:使用断言或异常进行合同设计?,Assertion VS Runtime异常,C++错误代码vs ASSERTS vs Exceptions选择选择:(,通过使用断言或例外的合同设计?等等)(*)还有书籍,如Herb Sutter的编码标准,谈论这个.共识似乎是这样的:
对于内部错误使用断言,因为模块的用户和开发人员是同一个人/团队.对其他一切使用例外.(**)
除了一件事,这条规则对我来说很有意义.我是一名科学家,使用C++进行科学模拟.在我的特定上下文中,这意味着我是我的大部分代码的唯一用户.如果我应用此规则,这意味着我永远不必使用例外?我想不会,例如,仍然存在I/O错误或内存分配问题,其中仍然需要例外.但除了我的程序与"外部世界"的那些交互之外,还有其他情况我应该使用异常吗?
根据我的经验,许多优秀的编程实践对我来说非常有用,尽管这些实践主要是为大型复杂系统或大型团队设计的,而我的程序主要是小型科学模拟,主要由我自己编写.因此这个问题.什么样的例外使用做法适用于我的背景?或者我应该只使用断言(以及I/O的异常,内存分配以及与"外部世界"的其他交互)?
(*)我希望在阅读完整的问题后,您同意这不是重复的.一般来说,异常vs断言的主题已经处理过了,但是,正如我在这里解释的那样,我不认为这些问题中的任何一个都能解决我的特殊情况.
(**)我用自己的话写下这个,试着恢复我读过的东西.如果您觉得它不能反映大多数人的共识,请随意批评这一陈述.
assert()是防止程序员犯错的保障措施,而异常则是针对其他存在的保障措施。
让我们用一个例子来解释这一点:
double divide(double a, double b) {
return a / b;
}
Run Code Online (Sandbox Code Playgroud)
这个函数的明显问题是如果 b == 0,你会得到一个错误。
现在,我们假设这个函数是用参数调用的,这些参数的值由你决定,而且只有你自己决定。您可以通过将函数更改为以下形式来检测问题:
double divide(double a, double b) {
ASSERT(b != 0);
return a / b;
}
Run Code Online (Sandbox Code Playgroud)
如果您不小心在代码中犯了一个错误,导致 b 可以采用 0 值,那么您就可以修复调用代码,方法是显式测试 0,或者确保在第一个代码中不会出现这种情况。地方。
只要此断言到位,您作为代码开发人员就会获得一定程度的保护。它是一个契约,可以让您轻松了解函数中可能发生的问题类型,尤其是在测试代码时。
现在,如果您无法控制传递给函数的值,会发生什么情况?该断言只会破坏程序的流程,而没有任何保护。
明智的做法是:
double divide(double a, double b) {
ASSERT(b != 0);
if (b == 0)
throw DivideByZeroException();
return a / b;
}
try {
result = divide(num, user_val);
} catch (DivideByZeroException & e) {
display_informative_message_to_user(e);
}
Run Code Online (Sandbox Code Playgroud)
请注意,断言仍然存在,因为它是最易读的指示可能出现问题的指示。但是,添加例外可以让您更轻松地从问题中恢复。
可以说这种方法是多余的,但在发布版本中,断言通常是 NOOP,没有生成代码,因此异常仍然是唯一的保护。
而且,这个函数非常简单,所以断言和异常抛出是立即可见的,但是添加了几十行代码,情况就不再是这样了。
现在,当您正在开发并且可能会犯错误时,断言失败将在其发生的确切行处可见,而异常可能会冒泡到不相关的 try/catch 块中,这将使准确定位问题变得更加困难,特别是当 catch 块不记录堆栈跟踪时。
因此,如果您希望安全并降低开发期间和正常执行期间出现错误的风险,您就永远不能太小心,并且可能希望以互补的方式提供这两种机制。
我会在希望检查对性能产生影响的地方使用断言。即,在编写简单类型的向量或矩阵类(例如double,complex<double>)时,我想进行边界检查,我会使用assert(),因为那里的检查可能会对性能产生很大的影响,因为它发生在每个元素访问中。然后我可以使用 关闭生产版本中的此检查-DNDEBUG。
如果检查的成本并不重要(例如,在将初始解决方案传递给迭代方案之前检查初始解决方案不包含 NaN 值),我将使用异常或在生产构建中也处于活动状态的其他机制。如果您的作业在集群队列中等待三天并运行 10 小时后中止,您至少希望有一个比“杀死 (SIGSEGV)”更好的诊断,这样您就可以避免在调试模式下重建,等待另一个3 天,另外花费 10 个小时昂贵的计算时间。
在这些情况下,异常和断言都不合适。一个例子是一个错误,其中检查的成本并不重要,但这仍然是致命的,以至于程序在任何情况下都无法继续。断言是不合适的,因为它只在调试模式下触发,异常是不合适的,因为它可能(意外地)被捕获,从而掩盖了问题。在这种情况下,我会使用自定义断言宏,它不依赖于NDEBUG,例如:
// This assert macro does not depend on the value of NDEBUG
#define assert_always(expr) \
do \
{ \
if(!(expr)) \
{ \
std::cerr << __FILE__ << ":" << __LINE__ << ": assert_always(" \
<< #expr << ") failed" << std::endl; \
std::abort(); \
} \
} while(false)
Run Code Online (Sandbox Code Playgroud)
(此示例取自此处,并修改了名称以表明稍微更广泛的用途)。