什么时候应该创建自己的异常类型?
When should you create your own exception type?
我正在做一个项目,我们正在将旧的 C 代码重组为新的 C++,我们使用异常处理错误。
我们正在为不同的模块创建不同的异常类型。
我不认为它值得,但我没有任何有效的论据来证明我的观点。因此,如果我们编写标准库,您会看到 vector_exception、list_exception 等等。
在思考的过程中,我偶然发现了这个问题:
When should you create your own exception type and when should you stick to the exceptions already created in std library?
另外,如果按照上面的方法,我们在不久的将来可能会遇到什么问题?
在下列情况下创建您自己的异常类型:
- 您可能希望在处理时区分它们。如果它们是不同的类型,您可以选择编写不同的
catch
子句。他们应该仍然有一个共同的基础,这样你就可以在适当的时候进行共同的处理
- 您想添加一些可以在处理程序中实际使用的特定结构化数据
有单独的 list
和 vector
异常似乎不值得,除非它们有一些独特的类似列表或矢量的东西。您是否真的要根据发生错误的容器类型进行不同的捕获处理?
相反,对于可能在运行时可恢复的事物,与回滚但可以重试的事物,与绝对致命或指示错误的事物,具有单独的异常类型可能是有意义的。
我认为有两个可能的原因。
1) 如果你想在异常中存储一些关于异常的自定义信息class,那么你需要一个带有额外数据成员的异常。您通常会继承标准异常之一 classes.
2) 如果要区分异常。例如,假设您有两种截然不同的 invalid argument
情况,并且在您的 catch
块中您希望能够区分这两种情况。您可以创建 std::invalid_argument
的两个子 classes
到处使用相同的异常很容易。特别是在试图捕获该异常时。不幸的是,它为 Pokemon exception handling 打开了大门。它带来了捕获意外异常的风险。
对所有不同的模块使用专用异常有几个优点:
- 每个案例的自定义消息,使其对报告更有用
- Catch 可以只捕获预期的异常,更容易在意外情况下崩溃(是的,这比不正确的处理更有优势)
- 崩溃时,IDE可以显示异常类型,更有助于调试
我认为除了其他答案中的原因之外可能是代码可读性,因为程序员花了很多时间来支持它。考虑两段代码(假设它们抛出 "empty frame" 错误):
void MyClass::function() noexcept(false) {
// ...
if (errorCondition) {
throw std::exception("Error: empty frame");
}
}
void MyClass::function() noexcept(false) {
// ...
if (errorCondition) {
throw EmptyFrame();
}
}
在第二种情况下,我认为它更具可读性,并且用户的消息(使用 what() 函数打印)隐藏在这个自定义异常中 class。
我发现 STL 的每个元素都会引发不同的内存异常知道处理列表异常。
我可以看到一些将异常与不同模块分开的逻辑,但我也可以看到拥有一个项目范围的异常基础的逻辑 class。我还可以看到具有异常类型 classes.
的逻辑
邪恶的联盟将建立一个异常层次结构:
std::exception
EXProjectBase
EXModuleBase
EXModule1
EXModule2
EXClassBase
EXClassMemory
EXClassBadArg
EXClassDisconnect
EXClassTooSoon
EXClassTooLate
然后坚持每个实际触发的异常都来自 BOTH 模块和 classification。然后,你可以捕获任何对捕获器有意义的东西,比如断开连接,而不是必须分别捕获 HighLevelDisconnect 和 LowLevelDisconnect。
另一方面,建议 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++,我们使用异常处理错误。
我们正在为不同的模块创建不同的异常类型。
我不认为它值得,但我没有任何有效的论据来证明我的观点。因此,如果我们编写标准库,您会看到 vector_exception、list_exception 等等。
在思考的过程中,我偶然发现了这个问题:
When should you create your own exception type and when should you stick to the exceptions already created in std library?
另外,如果按照上面的方法,我们在不久的将来可能会遇到什么问题?
在下列情况下创建您自己的异常类型:
- 您可能希望在处理时区分它们。如果它们是不同的类型,您可以选择编写不同的
catch
子句。他们应该仍然有一个共同的基础,这样你就可以在适当的时候进行共同的处理 - 您想添加一些可以在处理程序中实际使用的特定结构化数据
有单独的 list
和 vector
异常似乎不值得,除非它们有一些独特的类似列表或矢量的东西。您是否真的要根据发生错误的容器类型进行不同的捕获处理?
相反,对于可能在运行时可恢复的事物,与回滚但可以重试的事物,与绝对致命或指示错误的事物,具有单独的异常类型可能是有意义的。
我认为有两个可能的原因。
1) 如果你想在异常中存储一些关于异常的自定义信息class,那么你需要一个带有额外数据成员的异常。您通常会继承标准异常之一 classes.
2) 如果要区分异常。例如,假设您有两种截然不同的 invalid argument
情况,并且在您的 catch
块中您希望能够区分这两种情况。您可以创建 std::invalid_argument
到处使用相同的异常很容易。特别是在试图捕获该异常时。不幸的是,它为 Pokemon exception handling 打开了大门。它带来了捕获意外异常的风险。
对所有不同的模块使用专用异常有几个优点:
- 每个案例的自定义消息,使其对报告更有用
- Catch 可以只捕获预期的异常,更容易在意外情况下崩溃(是的,这比不正确的处理更有优势)
- 崩溃时,IDE可以显示异常类型,更有助于调试
我认为除了其他答案中的原因之外可能是代码可读性,因为程序员花了很多时间来支持它。考虑两段代码(假设它们抛出 "empty frame" 错误):
void MyClass::function() noexcept(false) {
// ...
if (errorCondition) {
throw std::exception("Error: empty frame");
}
}
void MyClass::function() noexcept(false) {
// ...
if (errorCondition) {
throw EmptyFrame();
}
}
在第二种情况下,我认为它更具可读性,并且用户的消息(使用 what() 函数打印)隐藏在这个自定义异常中 class。
我发现 STL 的每个元素都会引发不同的内存异常知道处理列表异常。
我可以看到一些将异常与不同模块分开的逻辑,但我也可以看到拥有一个项目范围的异常基础的逻辑 class。我还可以看到具有异常类型 classes.
的逻辑邪恶的联盟将建立一个异常层次结构:
std::exception
EXProjectBase
EXModuleBase
EXModule1
EXModule2
EXClassBase
EXClassMemory
EXClassBadArg
EXClassDisconnect
EXClassTooSoon
EXClassTooLate
然后坚持每个实际触发的异常都来自 BOTH 模块和 classification。然后,你可以捕获任何对捕获器有意义的东西,比如断开连接,而不是必须分别捕获 HighLevelDisconnect 和 LowLevelDisconnect。
另一方面,建议 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
块,因为较低级别的代码被设计为 抛出异常作为其正常流程的一部分 。 (我经常在读取不受信任的外部数据时这样做,其中很多事情 可以预见 出错。)
在这些极少数情况下,用户定义的类型是有意义的。