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

时间:2022-03-04 20:34:59

I am working on a project where we are restructuring old C code into new C++ where for error handling we are using exceptions.

我正在开发一个项目,我们将旧的C代码重构为新的C ++,我们正在使用异常进行错误处理。

We are creating different exception types for different modules.

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

I don't see it is worth but I don't have any valid argument to prove my point. So if we would have written the standard library, you would get to see vector_exception, list_exception and so on.

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

While thinking about it I stumbled upon this question:

在考虑这件事的同时,我偶然发现了这个问题:

When should you create your own exception type and when should you stick to the exceptions already created in std library?

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

Also, what could be the problems that we may face in the near future if we go by the above approach?

另外,如果我们采用上述方法,那么在不久的将来我们可能面临的问题是什么呢?

6 个解决方案

#1


25  

Create your own exception types when:

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

  1. you might want to differentiate them when handling. If they're different types, you have the option to write different catch clauses. They should still have a common base so you can have common handling when that is appropriate
  2. 您可能希望在处理时区分它们。如果它们是不同的类型,您可以选择编写不同的catch子句。它们仍然应该有一个共同的基础,以便在适当的时候进行共同处理

  3. you want to add some specific structured data you can actually use in the handler
  4. 您想要添加一些您可以在处理程序中实际使用的特定结构化数据

Having separate list and vector exceptions doesn't seem worthwhile, unless there's something distinctively list-like or vectorish about them. Are you really going to have different catch handling depending on which type of container had an error?

具有单独的列表和向量异常似乎并不值得,除非有类似于列表的东西或关于它们的矢量。你真的会根据哪种容器有错误而有不同的捕获处理吗?

Conversely it could make sense to have separate exception types for things that might be recoverable at runtime, versus things that are rolled-back but could be retried, versus things that are definitely fatal or indicate a bug.

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

#2


10  

Using the same exception everywhere is easy. Especially when trying to catch that exception. Unfortunately, it opens the door for Pokemon exception handling. It brings the risk of catching exceptions you don't expect.

在任何地方使用相同的例外很容易。特别是当试图抓住那个例外。不幸的是,它打开了口袋妖怪异常处理的大门。它带来了捕获您不期望的异常的风险。

Using a dedicated exception for all the different modules adds several advantages:

对所有不同的模块使用专用的例外添加了几个优点:

  • A custom message for each case, making it more useful for reporting
  • 每个案例的自定义消息,使其对报告更有用

  • Catch can capture only the intended exceptions, more easily crashes on unexpected cases (yes, thats an advantage over incorrect handling)
  • Catch只能捕获预期的异常,更容易在意外情况下崩溃(是的,这比错误处理更有优势)

  • On crash, IDE can show the exception type, helping even more with debugging
  • 崩溃时,IDE可以显示异常类型,通过调试帮助更多

#3


4  

I think that in addition to the reasons in other answers could be code readability, because a lot of time programmers spend supporting it. Consider two pieces of code (assuming they throw "empty frame" error):

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

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

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

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

In the second case I think it is more readable, and the message for the user (which printed with what() function) is hidden inside this custom exception class.

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

#4


2  

I see two possible reasons.

我看到两个可能的原因。

1) If you want to store in the exception class some custom information about the exception, then you need an exception with extra data members. Often you will inherit from one of the std exception classes.

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

2) If you want to differentiate between exceptions. For instance suppose you have two distinct invalid argument situations and in your catch block you want to be able to differeentiate between the two. You can create two child classes of std::invalid_argument

2)如果要区分异常。例如,假设您有两个不同的无效参数情况,并且在您的catch块中,您希望能够在两者之间区分。您可以创建两个std :: invalid_argument的子类

#5


2  

The issue I see with each element of the STL firing a distinct memory exception is that some parts of the stl are built on others, so queue would have to catch and translate list exceptions, or clients of queue would have to know to handle list exceptions.

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

I can see some logic in separating the exceptions from different modules, but I can also see the logic of having a project-wide exception base class. I can also see the logic of having exception type classes.

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

The unholy alliance would be to build an exception hierarchy:

邪恶联盟将建立一个异常层次结构:

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

Then insist that every actual fired exception is derived from BOTH a module and a classification. Then, you can catch whatever makes sense to the catcher, such as a disconnection, rather than Having to separately catch HighLevelDisconnect and LowLevelDisconnect.

然后坚持每个实际触发的异常都是从模块和分类中派生出来的。然后,您可以捕获捕获器有意义的任何内容,例如断开连接,而不是必须单独捕获HighLevelDisconnect和LowLevelDisconnect。

On the other hand it is also completely fair to suggest that the HighLevel interface should have completely dealt with LowLevel failures, and they should never be seen by the HighLevel API client. This is where catching by module would be a useful last-ditch feature.

另一方面,建议HighLevel接口应该完全处理LowLevel故障也是完全公平的,HighLevel API客户端永远不会看到它们。这是捕捉模块将是一个有用的最后沟渠功能的地方。

#6


1  

I would ask, of the try...catch section, "does this code know how to recover from the error, or not?

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

Code which uses try...catch is anticipating that an exception could happen. Reasons that you would write try...catch at all instead of letting the exception simply pass through are:

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

  1. Add some code to make the parent function exception safe. A lot of this should be done quietly with RAII in destructors, but sometimes (e.g. a move operation) need more work to roll-back a partially completed job, it depends on the level of exception safety you want.
  2. 添加一些代码以使父函数异常安全。应该在析构函数中使用RAII安静地完成大部分操作,但有时(例如移动操作)需要更多工作来回滚部分完成的作业,这取决于您想要的异常安全级别。

  3. Emit or add some debugging information and rethrow the exception.
  4. 发出或添加一些调试信息并重新抛出异常。

  5. Try to recover from the error.
  6. 尝试从错误中恢复。

For cases 1 and 2 you probably don't need to worry about user exception types, they will look like this

对于情况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;
}

This will probably by 99% of real world cases, in my experience.

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

Sometimes you will want to recover from the error. Maybe the parent function knows that a library will fail in certain conditions which can be fixed dynamically, or there is a strategy for re-attempting a job using different mechanisms.

有时您会想要从错误中恢复。也许父函数知道库在某些可以动态修复的条件下会失败,或者存在使用不同机制重新尝试作业的策略。

Sometimes the catch block will be written specifically because the lower-level code has been designed to throw exceptions as part of its normal flow. (I often do this when reading untrusted external data, where many things can predictably go wrong.)

有时候catch块会被专门编写,因为较低级别的代码被设计为将异常作为其正常流程的一部分。 (我经常在阅读不受信任的外部数据时执行此操作,其中许多事情可以预测会出错。)

In these, rarer cases, user-defined types make sense.

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

#1


25  

Create your own exception types when:

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

  1. you might want to differentiate them when handling. If they're different types, you have the option to write different catch clauses. They should still have a common base so you can have common handling when that is appropriate
  2. 您可能希望在处理时区分它们。如果它们是不同的类型,您可以选择编写不同的catch子句。它们仍然应该有一个共同的基础,以便在适当的时候进行共同处理

  3. you want to add some specific structured data you can actually use in the handler
  4. 您想要添加一些您可以在处理程序中实际使用的特定结构化数据

Having separate list and vector exceptions doesn't seem worthwhile, unless there's something distinctively list-like or vectorish about them. Are you really going to have different catch handling depending on which type of container had an error?

具有单独的列表和向量异常似乎并不值得,除非有类似于列表的东西或关于它们的矢量。你真的会根据哪种容器有错误而有不同的捕获处理吗?

Conversely it could make sense to have separate exception types for things that might be recoverable at runtime, versus things that are rolled-back but could be retried, versus things that are definitely fatal or indicate a bug.

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

#2


10  

Using the same exception everywhere is easy. Especially when trying to catch that exception. Unfortunately, it opens the door for Pokemon exception handling. It brings the risk of catching exceptions you don't expect.

在任何地方使用相同的例外很容易。特别是当试图抓住那个例外。不幸的是,它打开了口袋妖怪异常处理的大门。它带来了捕获您不期望的异常的风险。

Using a dedicated exception for all the different modules adds several advantages:

对所有不同的模块使用专用的例外添加了几个优点:

  • A custom message for each case, making it more useful for reporting
  • 每个案例的自定义消息,使其对报告更有用

  • Catch can capture only the intended exceptions, more easily crashes on unexpected cases (yes, thats an advantage over incorrect handling)
  • Catch只能捕获预期的异常,更容易在意外情况下崩溃(是的,这比错误处理更有优势)

  • On crash, IDE can show the exception type, helping even more with debugging
  • 崩溃时,IDE可以显示异常类型,通过调试帮助更多

#3


4  

I think that in addition to the reasons in other answers could be code readability, because a lot of time programmers spend supporting it. Consider two pieces of code (assuming they throw "empty frame" error):

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

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

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

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

In the second case I think it is more readable, and the message for the user (which printed with what() function) is hidden inside this custom exception class.

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

#4


2  

I see two possible reasons.

我看到两个可能的原因。

1) If you want to store in the exception class some custom information about the exception, then you need an exception with extra data members. Often you will inherit from one of the std exception classes.

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

2) If you want to differentiate between exceptions. For instance suppose you have two distinct invalid argument situations and in your catch block you want to be able to differeentiate between the two. You can create two child classes of std::invalid_argument

2)如果要区分异常。例如,假设您有两个不同的无效参数情况,并且在您的catch块中,您希望能够在两者之间区分。您可以创建两个std :: invalid_argument的子类

#5


2  

The issue I see with each element of the STL firing a distinct memory exception is that some parts of the stl are built on others, so queue would have to catch and translate list exceptions, or clients of queue would have to know to handle list exceptions.

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

I can see some logic in separating the exceptions from different modules, but I can also see the logic of having a project-wide exception base class. I can also see the logic of having exception type classes.

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

The unholy alliance would be to build an exception hierarchy:

邪恶联盟将建立一个异常层次结构:

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

Then insist that every actual fired exception is derived from BOTH a module and a classification. Then, you can catch whatever makes sense to the catcher, such as a disconnection, rather than Having to separately catch HighLevelDisconnect and LowLevelDisconnect.

然后坚持每个实际触发的异常都是从模块和分类中派生出来的。然后,您可以捕获捕获器有意义的任何内容,例如断开连接,而不是必须单独捕获HighLevelDisconnect和LowLevelDisconnect。

On the other hand it is also completely fair to suggest that the HighLevel interface should have completely dealt with LowLevel failures, and they should never be seen by the HighLevel API client. This is where catching by module would be a useful last-ditch feature.

另一方面,建议HighLevel接口应该完全处理LowLevel故障也是完全公平的,HighLevel API客户端永远不会看到它们。这是捕捉模块将是一个有用的最后沟渠功能的地方。

#6


1  

I would ask, of the try...catch section, "does this code know how to recover from the error, or not?

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

Code which uses try...catch is anticipating that an exception could happen. Reasons that you would write try...catch at all instead of letting the exception simply pass through are:

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

  1. Add some code to make the parent function exception safe. A lot of this should be done quietly with RAII in destructors, but sometimes (e.g. a move operation) need more work to roll-back a partially completed job, it depends on the level of exception safety you want.
  2. 添加一些代码以使父函数异常安全。应该在析构函数中使用RAII安静地完成大部分操作,但有时(例如移动操作)需要更多工作来回滚部分完成的作业,这取决于您想要的异常安全级别。

  3. Emit or add some debugging information and rethrow the exception.
  4. 发出或添加一些调试信息并重新抛出异常。

  5. Try to recover from the error.
  6. 尝试从错误中恢复。

For cases 1 and 2 you probably don't need to worry about user exception types, they will look like this

对于情况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;
}

This will probably by 99% of real world cases, in my experience.

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

Sometimes you will want to recover from the error. Maybe the parent function knows that a library will fail in certain conditions which can be fixed dynamically, or there is a strategy for re-attempting a job using different mechanisms.

有时您会想要从错误中恢复。也许父函数知道库在某些可以动态修复的条件下会失败,或者存在使用不同机制重新尝试作业的策略。

Sometimes the catch block will be written specifically because the lower-level code has been designed to throw exceptions as part of its normal flow. (I often do this when reading untrusted external data, where many things can predictably go wrong.)

有时候catch块会被专门编写,因为较低级别的代码被设计为将异常作为其正常流程的一部分。 (我经常在阅读不受信任的外部数据时执行此操作,其中许多事情可以预测会出错。)

In these, rarer cases, user-defined types make sense.

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