【问题标题】:What are the common usage of exceptions at catch site?捕获站点异常的常见用法是什么?
【发布时间】:2013-08-14 14:03:18
【问题描述】:

我对异常处理的理解非常有限。虽然我发现抛出异常很容易(或者我可以使用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 之间的映射以 mpl::map 的形式提供)?
  • 我可以做一些比打印消息/调用堆栈更有用的事情吗,比如使用不同的输入参数在 throw 站点恢复函数(比如当我得到 file_not_founddisk_error 时,重新运行函数不同的文件)?
  • 还有其他值得了解的模式吗?

谢谢

【问题讨论】:

    标签: c++ exception c++11 exception-handling try-catch


    【解决方案1】:

    这是一个很大的话题。

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

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

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

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

    3. 值得了解:始终通过 const 引用捕获异常:

      catch (const exception &amp; ex)

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

    对cmets的回答:

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

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

    【讨论】:

    • 如果我不将第三方异常映射到我自己的 API(不需要一对一),它们就会泄漏到客户端代码。为什么客户应该学习如何从第三方库获取相关信息,例如 e.source_type() 以获取源信息,如果我在内部使用的 boost::lexical_cast 例外(将来可能不会使用)用户提供的数据?另外,我为什么要通过常量引用来捕获异常?我捕获了一个异常来添加额外的上下文信息,或者从存储的信息中编写消息并打印。例如在boost::exceptione &lt;&lt; file_name("foo.txt");
    • 如果你通过常量引用捕获,你可以防止切片。请参阅 herbsutter.com/2008/01/01/… (Guru-Question) 或 stackoverflow.com/questions/18143604/… 了解对 const 引用的一些影响。此外,在更有效的 C++ 的第 13 项中有一章对此进行了讨论。
    • 为什么当我通过引用而不是常量引用捕获异常时会发生切片(如我的示例所示)。没有任何链接提供任何线索。这本书还讨论了通过引用(而不是常量引用)来捕获异常。 boost::exception 也有相同的建议,它特别建议在可用时放置其他信息,并在调用站点编写什么消息,以防止异常构造函数出现任何异常,并允许特定于语言环境的消息。你能详细说明一下常量引用和切片吗?
    • 常量引用比非常量引用有什么优势?将异常对象的所有权交给 catch 块是很公平的。
    • 我认为拥有const 的唯一原因是能够绑定到右值。切片不会发生或不相关。如果您收到basederived 引用,则无论如何您都不准备处理丢失的信息,因此它是否存在并不重要。就析构函数而言,使用const,调用原始析构函数。没有它,如果抛出临时变量,编译器将退出“非法初始化非常量引用”,否则使用虚拟调度。因此,除非库“损坏”(没有虚拟析构函数基类),否则没有真正的问题。
    【解决方案2】:

    除了 nogard 所说的我想添加以下内容:

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

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


    对cmets的回答:

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

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

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

    【讨论】:

    • 1.我明白。 2. 我可以很容易地登录析构函数,但这如何帮助我找到bug? 3. 具体怎么做?我正在寻找一些代码。我在哪里caught 一个异常肯定与它被抛出的地方不同。 4,5,6 我的示例显示了用法,并且只有一个我可以实现。最后,为了翻译异常,我将如何对其进行编码?寻找更具体的例子!
    • 您的第 1-6 点可能是我读过的最好和最简洁的例外方法。
    【解决方案3】:

    对于大型应用程序来说,顶级捕获通常是不够的。当您可以对异常采取一些措施时,您需要捕获异常,但处理异常主要只有几种方法:

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

    【讨论】:

      猜你喜欢
      • 2013-03-04
      • 2023-03-15
      • 1970-01-01
      • 1970-01-01
      • 2012-02-17
      • 2020-02-11
      • 2012-08-27
      • 2021-07-04
      • 2019-11-20
      相关资源
      最近更新 更多