项目级别的C++异常处理策略

4
假设我有嵌套的方法a,b,c,d,e,在每个级别上,我们在正常操作过程中返回错误,但是e也可能抛出异常(例如STL插入时内存不足)。这些异常很少发生,实际展开速度快慢并不重要。
在这种情况下,最合适的异常处理策略是什么?
1.将其放入最低级别并转换为普通错误条件。
优点:不需要编写异常安全代码,实现最简单,测试最容易,最容易理解,最小的编译时间信息用于展开。
缺点:看起来不太酷,添加明显的try/catch混乱 - 实际上每个插入和push_back周围都有,甚至需要编写STL容器的异常安全包装器,有人认为try块会有运行时性能损失(也有人认为根本没有损失)。
2.在顶部处理它。
优点:看起来很酷,没有混乱。
缺点:真的很难直观地验证之间的所有代码确实是异常安全的,测试所有异常展开路径将是一个挑战。
3.将其作为应用程序的完整重启在最顶层处理:清除未被异常处理清除的所有内容并重新开始。
优点:可预测,将容忍异常安全代码中的轻微瑕疵,比崩溃好得多。
缺点:过于严厉。
4.编写自定义分配器,允许在a()之前检查内存储备,然后再深入调用堆栈。
void a()
{
    ...
    x = b();
    ...
}

int b()
{
    y = c();

    ...

    return y + d();
}

int d()
{
    ...
    z = e();
    ...
}

我尝试格式化代码,但它不起作用,我不知道为什么。抱歉。 - Khaled Alshaya
这与列表后的代码有关。我只是添加了一个分隔符,我不知道其他的修复方法。 - GManNickG
也许你可以举个例子,说明为什么你认为在视觉上验证所有代码是否确实是异常安全的非常困难,测试所有异常解除路径将是一个挑战。如果你使用作用域对象,你不需要担心它如何解除。 - Greg Domjan
谢谢大家的帮助。我决定写一个异常安全的代码,而不在内部处理任何异常:因为在我的目标系统中,由于自定义STL分配器,异常是非常不可能发生的,它允许STL为最坏情况进行预留;在通用系统上,由于内存需求很小且编码仔细,异常的概率仍然很低,所以在调用者的代码中处理异常似乎是相当合适的。顺便说一下,这次讨论中可怕的部分是显然没有编译器验证异常安全性。 - uuu777
3个回答

6

通常情况下,我认为一个系统最好使用一种统一的错误处理策略 - 每次在两者之间转换时,都会有漏洞可以滑过去。大多数情况下,我更喜欢在任何合理支持异常的语言中使用异常(即不在C语言中 - setjmp / longjmp该死)。考虑到您描述的设计,大部分系统已经使用错误代码,因此似乎将异常转换为错误代码并在您的系统中假装不存在异常是有意义的。

另一方面,您确定只有e可以抛出异常吗?在C++中,从您可能不希望预期的地方抛出异常非常容易 - 调用new,几乎任何标准库的使用等。

请注意,在具有类似RAII的技术的异常安全性方面,当您有以下代码时也很有用:

int* workspace[] = new int[500];
...
if(some_function() == ERROR)
  return SOME_FUNCTION_FAILED; // oops just leaked memory
...
delete[] workspace;

(或是任何需要获取资源的操作)

RIAA技术被认为与异常处理有关,但实际上这只是因为在异常处理中这些技术非常必要(因为你没有其他方法可以做到),而在错误码处理技术中它们只是非常有用;虽然理论上你可以自己处理所有的资源释放,但实际情况下你会忘记一些事情。让编译器来完成吧。

你真的需要在每个操作周围都加上try/catch吗?为什么不将整个函数封装在try/catch中并返回一个错误呢?首先,这样可以更容易地区分例如读取错误的std::vector索引和抛出std::bad_alloc的内存分配失败之类的故障。

try/catch的性能惩罚取决于ABI和编译器。我相信在现代ABI(例如x86-64 Unix ABI)和现代GCC上,成本是非零但最小的,但在其他编译器上可能会明显一些。如果你真的想知道,你必须在你特定的编译器/平台上运行实验。


说得好。还要记住像函数尝试块这样的野兽。其他资源:C++ FAQ: http://www.parashift.com/c++-faq-lite/exceptions.html 和 Stroustrup on Exceptions: http://www.giref.ulaval.ca/~ctibirna/work/readings/stroustrup-pwex.html - dirkgently
+1,当你涉及到多个退出路径函数时,你需要尽快掌握“异常安全”。请注意,如果你编写基于返回错误代码的代码,通常可以在C++中禁用异常支持。 - Matthieu M.

1
无论你在这里做出什么决定,我都会鼓励你向其他开发人员灌输异常安全的概念。根据我的经验,编写异常安全代码的过程会导致更清晰、更事务性的代码设计。
作为一个好处,这种编码风格不受异常存在与否的影响,而反过来则不成立。

0

我会根据几个因素做出决定。

1)您的代码库有多干净。如果您的代码库相对干净,那么验证代码是否异常安全并在顶部捕获异常会更容易。但是,如果代码是杂乱无章的,那么在低级别上捕获异常并依赖现有的错误处理将更容易。

2)你的团队有多强。假设你不是唯一一个编码的人,另一个需要处理的问题是你的所有团队成员是否都能编写异常安全的代码。如果您的团队中有人无法打破旧习惯,您应该在低级别上捕获异常,因为这样的程序员可能会随着时间推移引入不安全的异常代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接