您应该在什么时候创建自己的异常类型
When should you create your own exception type?
我正在进行一个项目,我们正在将旧的C代码重组为新的C++,为了处理错误,我们使用了异常。
我们正在为不同的模块创建不同的异常类型。
我认为这不值得,但我没有任何有效的论据来证明我的观点。因此,如果我们编写了标准库,您将看到vector_exception、list_exception等
在思考的时候,我偶然发现了这个问题:
什么时候应该创建自己的异常类型,什么时候应该坚持std库中已经创建的异常?
此外,如果采用上述方法,我们在不久的将来可能会面临什么问题?
在以下情况下创建自己的异常类型:
- 您可能希望在处理时区分它们。如果它们是不同的类型,则可以选择编写不同的
catch
子句。它们仍然应该有一个共同的基础,这样你就可以在适当的时候进行共同的处理 - 您希望添加一些可以在处理程序中实际使用的特定结构化数据
拥有单独的list
和vector
异常似乎是不值得的,除非它们有一些明显的列表式或矢量化的东西。根据哪种类型的容器出现错误,您真的会有不同的捕获处理吗?
相反,对于可能在运行时恢复的事情,与回滚但可以重试的事情,以及肯定致命或指示错误的事情,使用单独的异常类型是有意义的。
在任何地方使用相同的异常都很容易。尤其是在试图捕捉异常时。不幸的是,它为口袋妖怪异常处理打开了大门。它带来了捕捉到你意想不到的异常的风险。
为所有不同的模块使用专用异常增加了几个优点:
- 每个案例的自定义消息,使其对报告更有用
- Catch只能捕获预期的异常,更容易在意外情况下崩溃(是的,这比不正确的处理更有优势)
- 在崩溃时,IDE可以显示异常类型,甚至有助于调试
我认为除了其他答案中的原因外,还有代码可读性,因为程序员花了很多时间来支持它
void MyClass::function() noexcept(false) {
// ...
if (errorCondition) {
throw std::exception("Error: empty frame");
}
}
void MyClass::function() noexcept(false) {
// ...
if (errorCondition) {
throw EmptyFrame();
}
}
在第二种情况下,我认为它更可读,并且用户的消息(用what()函数打印)隐藏在这个自定义异常类中。
我看到了两个可能的原因。
1) 如果您想在异常类中存储一些关于异常的自定义信息,那么您需要一个具有额外数据成员的异常。通常,您将从std异常类之一继承。
2) 如果要区分异常。例如,假设您有两种不同的invalid argument
情况,并且在catch
块中,您希望能够区分这两种情况。您可以创建std::invalid_argument
的两个子类
我看到的STL的每个元素触发不同内存异常的问题是,STL的某些部分是在其他部分上构建的,因此队列必须捕获和转换列表异常,或者队列的客户端必须知道如何处理列表异常。
我可以看到将异常从不同模块中分离出来的一些逻辑,但我也可以看到拥有项目范围的异常基类的逻辑。我还可以看到拥有异常类型类的逻辑。
邪恶的联盟将建立一个例外层次:
std::exception
EXProjectBase
EXModuleBase
EXModule1
EXModule2
EXClassBase
EXClassMemory
EXClassBadArg
EXClassDisconnect
EXClassTooSoon
EXClassTooLate
然后坚持每个实际激发的异常都是从模块和分类中派生的。然后,你可以捕捉任何对捕捉者有意义的东西,比如断开连接,而不是必须分别捕捉HighLevelDisconnect和LowLevelDiscovery。
另一方面,建议HighLevel接口应该完全处理LowLevel故障也是完全公平的,并且它们永远不应该被HighLevel API客户端看到。在这里,模块捕捉将是一个有用的最后一搏功能。
对于try...catch
部分,我想问,"这段代码知道如何从错误中恢复吗?
使用try...catch
的代码预计可能会发生异常。您编写try...catch
而不是简单地让异常通过的原因是:
- 添加一些代码以确保父函数异常的安全。很多工作都应该在析构函数中使用RAII悄悄完成,但有时(例如移动操作)需要更多的工作来回滚部分完成的工作,这取决于您想要的异常安全级别
- 发出或添加一些调试信息并重新引发异常
- 尝试从错误中恢复
对于情况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
块会被专门编写,因为较低级别的代码被设计为抛出异常,作为其正常流程的一部分。(我经常在读取不受信任的外部数据时这样做,在这些数据中,许多事情都可能出现错误。)
在这种罕见的情况下,用户定义的类型是有意义的。
- 没有为自己的结构调用列表推回方法
- 在他自己的方法中,有可能将一个对象取消引用到另一个对象吗
- 在c++中为我自己的基于指针的数组分配内存的正确方法
- 在信号处理程序中捕获C++未处理的异常并恢复应用程序
- C++从对象自己的类中删除对象
- 使用 std::optional,而不是自己的结构
- 在C++程序中使用的迭代器中未处理的异常
- 您应该在什么时候创建自己的异常类型
- 尝试编写我自己的字符串类在 gcc 中出现异常
- 为什么要创建自己的自定义异常类
- 创建自己的异常(2种方法)C
- 在QT中创建我自己的异常并在函数中抛出异常
- 我想捕获一个异常,并将其绑定到我自己的异常中,然后向上抛出
- 不能抛出C++中自己的异常
- boost.log std::异常格式化程序找不到运算符<< 自己的命名空间中的重载
- 用c++定义自己的异常类
- 从其基类捕获的自己的异常类调用方法
- Poco是否有自己的附加异常类或模式
- C++/Cereal:从 JSON 反序列化为自己类时的异常
- 如果我自己的构造函数必须在不诉诸"new"的情况下抛出,如何缓解聚合对象构造函数的异常?