什么时候应该创建自己的异常类型?

时间:2018-10-01 10:31:33

标签: c++ exception exception-handling

我正在一个项目中,我们将旧的C代码重组为新的C ++,在该项目中,我们使用异常来处理错误。

我们正在为不同的模块创建不同的异常类型。

我不认为这是值得的,但是我没有任何有效的论据来证明我的观点。因此,如果我们要编写标准库,您将看到vector_exception,list_exception等。

在思考时,我偶然发现了这个问题:

  

何时应创建自己的异常类型,何时应坚持在std库中已创建的异常?

如果采用上述方法,不久的将来我们可能会遇到什么问题?

6 个答案:

答案 0 :(得分:25)

在以下情况下创建自己的异常类型:

  1. 您可能希望在处理时区分它们。如果它们是不同的类型,则可以选择编写不同的catch子句。他们应该仍然有一个共同的基础,以便您可以在适当的时候进行共同的处理
  2. 您要添加一些可以在处理程序中实际使用的特定结构化数据

分别拥有listvector异常似乎并不值得,除非它们之间有明显的类似列表或矢量的东西。您是否真的会根据发生错误的容器类型进行不同的捕获处理?

相反,对于可能在运行时可恢复的事物,对于已回滚但可以重试的事物,对于绝对致命或表明错误的事物,使用单独的异常类型可能是有意义的。

答案 1 :(得分:10)

在任何地方使用相同的异常很容易。特别是在尝试捕获该异常时。不幸的是,它为Pokemon exception handling打开了大门。这就带来了捕获意外异常的风险。

为所有不同的模块使用专用异常会增加一些优点:

  • 针对每种情况的自定义消息,使其更易于报告
  • Catch只能捕获预期的异常,在意外情况下更容易崩溃(是的,这是对错误处理的一个优势)
  • 崩溃时,IDE可以显示异常类型,从而在调试方面提供更多帮助

答案 2 :(得分:5)

我认为除其他答案外,原因还可能是代码可读性,因为程序员花费大量时间来支持它。考虑两段代码(假设它们抛出“空帧”错误):

void MyClass::function() noexcept(false) {

    // ...
    if (errorCondition) {
        throw std::exception("Error: empty frame");
    }
}
void MyClass::function() noexcept(false) {

    // ...
    if (errorCondition) {
        throw EmptyFrame();
    }
}

在第二种情况下,我认为它更具可读性,并且用户的消息(使用what()函数打印)隐藏在此自定义异常类中。

答案 3 :(得分:2)

我看到两个可能的原因。

1)如果要在异常类中存储一些有关该异常的自定义信息,则需要一个带有额外数据成员的异常。通常,您将从一个std异常类中继承。

2)如果要区分例外。例如,假设您有两种不同的invalid argument情况,并且在catch块中希望能够区分这两种情况。您可以创建std::invalid_argument

的两个子类。

答案 4 :(得分:2)

我看到的STL的每个元素触发一个明显的内存异常的问题是stl的某些部分是建立在其他部分之上的,因此队列将不得不捕获并转换列表异常,否则队列的客户将不得不知道处理列表异常。

我可以看到一些将异常与不同模块分开的逻辑,但是我也可以看到拥有一个项目范围的异常基类的逻辑。我还可以看到拥有异常类型类的逻辑。

邪恶的联盟将建立一个异常等级体系:

std::exception
  EXProjectBase
    EXModuleBase
      EXModule1
      EXModule2
    EXClassBase
      EXClassMemory
      EXClassBadArg
      EXClassDisconnect
      EXClassTooSoon
      EXClassTooLate

然后坚持认为,每个实际触发的异常均来自两者模块和分类。然后,您可以捕获对捕获程序有意义的任何内容(例如断开连接),而不必分别捕获HighLevelDisconnect和LowLevelDisconnect。

另一方面,建议HighLevel接口应该完全处理LowLevel故障,并且HighLevel API客户端永远不要看到它们,这也是完全公平的。在这里,按模块捕获将是有用的最后一击功能。

答案 5 :(得分:1)

我会在try...catch部分中问:“此代码是否知道如何从错误中恢复?

使用try...catch的代码预期会发生异常。您会写try...catch 而不是让异常简单通过的原因是

  1. 添加一些代码以使父函数异常安全。很多这样的事情应该在析构函数中使用RAII悄悄完成,但是有时(例如,移动操作)需要更多的工作来回滚部分完成的工作,这取决于所需的异常安全级别。
  2. 发出或添加一些调试信息,然后重新引发异常。
  3. 尝试从错误中恢复。

对于情况1和2,您可能不必担心用户异常类型,它们看起来像这样

try {
    ....
} catch (const std::exception & e) {
    // print or tidy up or whatever
    throw;
} catch (...) {
    // print or tidy up or whatever
    // also complain that std::exception should have been thrown
    throw;
}

根据我的经验,这可能占现实案例的99%。

有时您会希望从错误中恢复。父函数可能知道某个库在某些情况下会失败,这些情况可以动态修复,或者有一种使用不同机制重新尝试作业的策略。

有时catch块将被专门编写,因为低级代码已被设计为将异常作为其正常流程的一部分抛出。 (在读取不受信任的外部数据时,我经常这样做,因为许多事情可能可预测地出问题。)

在这种罕见的情况下,用户定义的类型才有意义。