我正在一个项目中,我们将旧的C代码重组为新的C ++,在该项目中,我们使用异常来处理错误。
我们正在为不同的模块创建不同的异常类型。
我不认为这是值得的,但是我没有任何有效的论据来证明我的观点。因此,如果我们要编写标准库,您将看到vector_exception,list_exception等。
在思考时,我偶然发现了这个问题:
何时应创建自己的异常类型,何时应坚持在std库中已创建的异常?
如果采用上述方法,不久的将来我们可能会遇到什么问题?
答案 0 :(得分:25)
在以下情况下创建自己的异常类型:
catch
子句。他们应该仍然有一个共同的基础,以便您可以在适当的时候进行共同的处理分别拥有list
和vector
异常似乎并不值得,除非它们之间有明显的类似列表或矢量的东西。您是否真的会根据发生错误的容器类型进行不同的捕获处理?
相反,对于可能在运行时可恢复的事物,对于已回滚但可以重试的事物,对于绝对致命或表明错误的事物,使用单独的异常类型可能是有意义的。
答案 1 :(得分:10)
在任何地方使用相同的异常很容易。特别是在尝试捕获该异常时。不幸的是,它为Pokemon exception handling打开了大门。这就带来了捕获意外异常的风险。
为所有不同的模块使用专用异常会增加一些优点:
答案 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和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
块将被专门编写,因为低级代码已被设计为将异常作为其正常流程的一部分抛出。 (在读取不受信任的外部数据时,我经常这样做,因为许多事情可能可预测地出问题。)
在这种罕见的情况下,用户定义的类型才有意义。