7

我对异常处理的理解非常有限。虽然我发现抛出异常很容易(或者我可以将其打包以expected<T>备后用),但我对如何处理异常知之甚少。

目前我的知识仅限于

  • 清理我自己的资源并重新抛出要在适当位置处理的异常。例如

    ptr p = alloc.allocate(n);
    try
    {
       uninitialized_copy(first,last,p);//atomic granularity, all or none
    }
    catch(...)
    {
        alloc.deallocate(p,n);
        throw;
    }
    

但我想,这可以等效地转换为RAII模式

alloc_guard<ptr> p{alloc.allocate(n)};
uninitialized_copy(first,last,p.get());
p.commit();
  • 在顶层捕获异常,撰写并打印一条好消息并退出。例如

    int main(int argc,char** argv)
    {
       try
       {
           app_t the_app(argc,argv);
           the_app.run();
       }
       catch(std::runtime_error& e)
       {
          //instead of what, I can also compose the mesage here based on locale.
          std::cout<<e.what()<<std::endl;
       }
    }
    

因此,我所做的只是在顶级函数中,例如main捕获异常并打印适当的消息并关闭。

在使用各种外部库作为实现的后端来实现具有一组很好的 API 的库时,我意识到第三方库异常是我的 API 规范的一部分,因为它们跨越了我的库边界并进入了用户代码!

因此,我的库 API 将我正在使用的外部库(并且每个库都有自己的异常层次结构)中的所有异常泄露给了用户代码。

这引出了我的问题,当我发现任何异常时可以做什么?

进一步来说,

  • 我可以将捕获的异常从外部库转换为我自己的异常并以通用方式抛出它(比如第三方库异常层次结构和我的异常 API 之间的映射作为 a 提供mpl::map)?
  • 我可以做一些比打印消息/调用堆栈更有用的事情吗,比如使用不同的输入参数在 throw 站点恢复函数(比如当我得到 a file_not_foundor时disk_error,使用不同的文件重新运行函数)?
  • 还有其他值得了解的模式吗?

谢谢

4

3 回答 3

4

除了 nogard 所说的之外,我还想添加以下内容:

  1. 例外应该用于例外的事情。不应该发生的事情。
  2. 如果您遇到异常,请至少在某个地方记录它。这可能会帮助您找到错误。
  3. 尝试在您发现异常时解决错误。
  4. 如果这是不可能的,请尝试保持应用程序可以继续的一致状态。
  5. 如果这是不可能的 - 考虑优雅地终止。
  6. 通知用户发生了不寻常的事情。

最后的建议 - 保持你的错误处理一致。这包括将第三方库中的异常转换为您的异常层次结构。


评论回复:

2) 异常应包含有关出现问题的信息。这可能只是类型或一些附加信息。在客户处记录这些信息可以让您获得更多关于实际问题的信息,而不是客户告诉您的内容。也许他滥用了您的应用程序,发现了另一个用例,或者您只是有一个错误(例如未初始化的变量)。但是有了这些额外的信息,您就有了出错的位置和一些关于出错的信息。这可以帮助您推断错误的来源,从而找到错误。

3)这实际上取决于正在发生的错误。例如,您尝试访问一个不存在的配置文件 --> 您创建一个具有默认值的新配置文件。客户端尝试打开数据库进行写访问,但它是写保护的。您拒绝打开,返回有效状态并告诉客户端数据库被写保护。内存不足无法继续?记录这个(注意 - 你没有多余的内存,所以你的记录应该已经为这个用例预留了一些内存)并优雅地关闭应用程序。也许,如果可能的话,通知客户。

关于来自其他库的代码:没有其他方法可以检查对另一个库的每个函数调用以查找它可能返回的异常并捕获它们。一旦被捕获,您可以将该异常中的信息传输到您的异常中并抛出该异常(或以其他方式解决它)

于 2013-08-13T06:03:39.010 回答
2

这是一个很大的话题。

  1. 我怀疑您是否可以轻松地将第三方异常转换为您自己的异常,而且我个人认为没有必要实现此行为。由于 3rd 方库是您实现的一部分,它不暴露给公共 API,为什么要暴露它的所有异常(即使通过一些映射)?如果有一天你坚持使用另一个 3rd 方库来实现相同的东西 - 你想重新设计整个异常层次结构吗?我想不是。您的库的 API 不能脆弱,因此我建议不要将外部异常映射到您自己的异常。

  2. 您可以通过以下方式将第 3 方例外映射到您的层次结构:

    • 根本不要包裹任何东西。我的意思是你不必仅仅因为第三个图书馆这样做就扔任何东西。您可以捕获该异常并进行处理,或者返回错误代码,或者适当地更改状态。还有很多其他的可能性,而不是总是重新抛出。

    • 您不必为所有第 3 方例外情况进行一对一的翻译。如果您在内部使用库 AAA,那么您可以使用单个 AAAException 来表示来自该库的许多异常。

  3. 有价值的知识:总是通过 const 引用捕获异常:

    catch (const exception & ex)

这个题目很大,希望我的回答有助于理解。

对评论的回答:

  1. 如果我不将第三方异常映射到我自己的 API(不必是一对一的),它们会泄漏到客户端代码- 不,它们不会,这就是重点!您必须在库中捕获它们,然后决定如何处理捕获的异常:抛出您自己的异常、返回错误代码、通知客户端侦听器、记录错误等...

    try {
        3rdpatry.call();
    } catch (const 3rdpartyException & ex) {
        // throw YourException(ex.what());
        // listener.notify(some_error)
        // return some_code
    }
    
  2. 通过 const 引用捕获根本不是为了防止切片。这里有很好的讨论来解释这一点。

于 2013-08-13T05:35:22.777 回答
2

对于大型应用程序,顶级捕获通常是不够的。当你可以对它做一些事情时,你需要捕获异常,但基本上只有几种方法可以处理异常:

  1. 如果可以,请恢复。- (例如:检查更新 -> 网络连接无法打开异常 -> 忽略并且现在不要下载更新。)
  2. 告诉用户选择如何恢复。(例如:保存文件 -> 无法创建文件异常 -> 告诉用户选择不同的文件名或取消)
  3. 登录并退出。这是顶级的包罗万象的场景。
于 2013-08-13T08:56:25.220 回答