如何设计异常“类型”?在C ++中 [英] How to design exception "types" in C++

查看:93
本文介绍了如何设计异常“类型”?在C ++中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我开发的大多数代码库中,两种非常常见的反模式是布尔值返回值(指示成功/失败)和通用整数返回值(指示错误消息的更多详细信息)。

Two anti-patterns that are incredibly common in most code bases I've worked out of are Boolean return values to indicate success/failure, and generic integral return codes to indicate more details about an error message.

这两个都是非常类似于C的语言,在我的拙见中并不太适合C ++。

Both of these are very C-like and do not fit into C++ very well in my humble opinion.

我的问题是关于在代码库中设计异常的最佳实践。换句话说,指示失败的有限可能性的最佳方法是什么?例如,上述反模式之一通常具有一个巨型枚举,每个枚举值代表一种特定的故障,例如 FILE_DOES_NOT_EXIST NO_PERMISSIONS 。通常,这些内容应尽可能通用,以便可以在多个不相关的域中使用(例如网络组件和文件I / O组件)。

My question is in regards to best practices when it comes down to designing exceptions into your code base. In other words, what is the best way to indicate the finite possibilities for failure? For example, one of the aforementioned anti-patterns would typically have one giant enumeration with each enumeration value representing a specific kind of failure, such as FILE_DOES_NOT_EXIST or NO_PERMISSIONS. Normally these are kept as general as possible so that they can be used across multiple, unrelated domains (such as networking components and file I/O components).

类似的设计为此,可能会考虑的异常是从 std :: exception 继承一种具体的异常类型,用于每种类型的故障或可能出错的事情。因此,在上一个示例中,我们将具有以下内容:

A design similar to this that one might consider for exceptions is to subclass one concrete exception type from std::exception for each type of failure or thing that might go wrong. So in my previous example, we would have the following:

namespace exceptions {
  class file_does_not_exist : public std::exception {};
  class no_permissions : public std::exception {};
}

我认为这更接近于感觉更好的东西,但是在结束,这似乎是一场维护噩梦,尤其是如果您有数百个错误代码要转换成类时。

I think this is closer to something that "feels better", but in the end this just seems like a maintenance nightmare, especially if you have hundreds of these "error codes" to translate over into classes.

我见过的另一种方法是使用标准的< stdexcept> 类,例如 std :: runtime_error 并有一个包含具体内容的字符串。例如:

Another approach I've seen is to simply use the standard <stdexcept> classes, such as std::runtime_error and have a string with the specifics. For example:

throw std::runtime_error( "file does not exist" );
throw std::runtime_error( "no permissions" );

此设计更具可维护性,但如果有条件地捕获这两个异常中的任何一个,它们将变得困难或不可行

This design is much more maintainable but makes it difficult or unfeasible to conditionally catch either of these exceptions should they both be potentially thrown from the same core location or function call.

那么对于异常类型来说,一个好的,可维护的设计是什么呢?我的要求很简单。我想获得有关所发生情况的上下文信息(我是否用完了内存?我是否缺少文件系统权限?是否无法满足函数调用的先决条件(例如错误的参数)?),我也想以便能够据此采取行动。也许我对所有这些人都一视同仁,也许我对某些失败有特定的catch语句,所以我可以从它们中以不同的方式恢复。

So what would be a good, maintainable design for exception types? My requirements are simple. I'd like to have contextual information about what happened (did I run out of memory? Do I lack filesystem permissions? Did I fail to meet the preconditions of a function call (e.g. bad parameters)?), and I'd also like to be able to act on that information accordingly. Maybe I treat all of them the same, maybe I have specific catch statements for certain failures so I can recover from them differently.

我对此的研究仅导致我这个问题:
C ++异常类设计

My research on this has only lead me to this question: C++ exception class design

这里的用户问我一个类似的问题,他/她底部的代码示例几乎很讨人喜欢,但他/她的基本异常类不遵循open /

The user here asks a similar question that I am, and his/her code sample at the bottom is almost likable, but his/her base exception class does not follow the open/closed principle, so that wouldn't really work for me.

推荐答案

C ++标准库的异常层次结构非常随意,恕我直言毫无意义例如,如果有人开始实际使用例如 std :: logic_error 而不是在很明显该程序具有非常讨厌的Bug™时终止。如标准所言,

The C++ standard library’s exception hierarchy is IMHO pretty arbitrary and meaningless. For example, it would probably just create problems if anyone started actually using e.g. std::logic_error instead of terminating when it’s clear that the program has a Very Nasty Bug™. For as the standard puts it,


“逻辑错误的显着特征是它们是由于内部逻辑的错误引起的。程序。

“The distinguishing characteristic of logic errors is that they are due to errors in the internal logic of the program.”

因此,在其他情况下抛出 std似乎很合理:逻辑错误程序状态可能无法预料地被弄坏了,并且继续执行可能会使用户的数据受到损害。

Thus, at the point where it might otherwise seem reasonable to throw a std::logic_error the program state might be unpredictably fouled up, and continued execution might put the user’s data in harm’s way.

仍然,像 std :: string 一样,标准异常类层次结构具有真正非常重要和有用的功能,即它是正式标准

Still, like std::string the standard exception class hierarchy has a really really practically important and useful feature, namely that it’s formally standard.

因此,任何自定义异常类都应间接地(或尽管我不推荐)直接从 std :: exception 派生。 。

So any custom exception class should be derived indirectly or (although I would not recommend it) directly from std::exception.

通常,十年前关于自定义异常类的争论激增时,我建议仅从 std :: runtime_error导出 code> ,我仍然建议这样做。它是支持自定义消息的标准异常类(其他消息通常具有硬编码消息,一个消息最好不要更改,因为它们具有可识别的价值)。也许有人会争辩说 std :: runtime_error 是代表可恢复的故障的标准异常类(与不可恢复的逻辑错误相对em>(在运行时无法修复),或者按照标准的说法,

Generally, when the debates about custom exception classes raged ten years ago, I recommended deriving only from std::runtime_error, and I still recommend that. It is the standard exception class that supports custom messages (the others generally have hardcoded messages that one preferably should not change, since they have value in being recognizable). And one might argue that std::runtime_error is the standard exception class that represents recoverable failures (as opposed to unrecoverable logic errors, which can’t be fixed at run time), or as the standard puts it,


运行时错误是由于超出程序范围的事件。不能很容易地预先预测它们。

“runtime errors are due to events beyond the scope of the program. They cannot be easily predicted in advance”.

有时C ++异常机制用于其他用途,被视为低级动态目标跳转机制。例如,聪明的代码可以使用异常从一系列递归调用中传播成功的结果。但是,异常失败是最常见的用法,因此通常会针对C ++异常进行优化,因此在大多数情况下,将 std :: runtime_error 用作root是有意义的用于任何自定义异常类层次结构–即使这迫使一个想变得聪明的人也抛出了失败-表示成功的异常提示。

Sometimes the C++ exception mechanism is used for other things, treated as just a low-level dynamic destination jump mechanism. For example, clever code can use exceptions for propagating a successful result out of a chain of recursive calls. But exception-as-failure is the most common usage, and that’s what C++ exceptions are typically optimized for, so mostly it makes sense to use std::runtime_error as root for any custom exception class hierarchy – even if that forces someone who wants to be clever, to throw a “failure”-indicating exception to indicate success…

值得一提的是: std :: runtime_error ,即 std :: range_error std :: overflow_error std :: underflow_error ,这与它们的名称相反,后者不需要由浮点运算生成,实际上也不是生成通过浮点运算,但AFAIK仅由某些–惊喜! – std :: bitset 操作。简而言之,在我看来,标准库的异常类层次结构只是出于外观考虑而被扔到那里,没有任何真正的正当理由或现有的实践,甚至没有做一下做的检查。但是也许我错过了,如果是这样,那么我仍然有一些新的知识要学习。 :-)

Worth noting: there are three standard subclasses of std::runtime_error, namely std::range_error, std::overflow_error and std::underflow_error, and that contrary to what their names indicate the latter two are not required to be be generated by floating point operations and are not in practice generated by floating point operations, but are AFAIK only generated by some – surprise! – std::bitset operations. Simply put, the standard library’s exception class hierarchy seems to me to have been thrown in there just for apperance’s sake, without any real good reasons or existing practice, and even without a does-it-make-sense check. But maybe I missed out on that and if so, then I still have something new to learn about this. :-)

那么, std :: runtime_error 就是这样。

在自定义异常类层次结构的顶部,对于C ++ 03,添加C ++ 03标准异常中缺少的重要内容非常有用:

At the top of a hierarchy of custom exception classes, with C++03 it was useful to add in the important stuff missing from C++03 standard exceptions:


  • 虚拟 clone 方法(对于通过C代码传递异常特别重要)。

  • Virtual clone method (especially important for passing exceptions through C code).

虚拟 throwSelf 方法(与克隆相同的主要原因)。

Virtual throwSelf method (same main reason as for cloning).

支持链接的异常消息(标准化格式)。

Support for chained exception messages (standardizing a format).

支持携带失败原因代码(例如Windows或Posix错误代码)。

Support for carrying a failure cause code (like e.g. Windows or Posix error code).

支持从携带的故障原因代码中获取标准消息。

Support for getting a standard message from a carried failure cause code.

C ++ 11增加了对大多数功能的支持,但是除了尝试了对失败原因代码和消息的新支持之外,并遗憾地指出,它非常特定于Unix,并且不太适合Windows,我有尚未使用。无论如何,出于完整性考虑:不要添加克隆和虚拟重新抛出(这是普通应用程序程序员在自定义异常类层次结构中可以做到的最好,因为作为应用程序程序员,您不能将当前异常对象从实现中的存储中提升出来。 ; s的异常传播使用),C ++ 11标准添加了免费函数 std :: current_exception() std :: rethrow_exception(),而不是支持链接的异常消息,而是添加了一个mixin类 std :: nested_exception 和自由函数 std :: rethrow_nested std :: rethrow_if_nested

C++11 added support for much of this, but except for trying out the new support for failure cause codes and messages, and noting that unfortunately it’s pretty Unix-specific and not very suitable for Windows, I haven’t yet used it. Anyway, for completeness: instead of adding cloning and virtual rethrowing (which is the best that an ordinary application programmer can do in a custom exception class hierarchy, because as an application programmer you cannot hoist a current exception object out of the storage that the implementation’s exception propagation uses), the C++11 standard adds free functions std::current_exception() and std::rethrow_exception(), and instead of support for chained exception messages it adds a mixin class std::nested_exception and free functions std::rethrow_nested and std::rethrow_if_nested.

鉴于C ++ 11对上面的要点是,新的和现代的自定义异常类层次结构应该更好地与C ++ 11支持集成在一起,而不是解决C ++ 03的缺点。好吧,除了C ++ 11失败代码外,这似乎非常不适合Windows编程。因此,在自定义层次结构的顶部,在 std :: runtime_error 的正下方,理想情况下至少应有一个通用异常类,并从该异常类派生出一个支持传播失败代码。

Given the partial C++11 support for the above bullet points, a new and modern custom exception class hierarchy should better integrate with the C++11 support instead of addressing the C++03 shortcomings. Well, except for the C++11 failure code thing, which seems to be very unsuitable for Windows programming. So, at the top of the custom hierarchy, right under std::runtime_error, there will ideally be at least one general exception class, and derived from that, one exception class that supports propagation of failure codes.

现在,最后要问的问题是:现在应该最好地为每种可能的失败原因派生唯一的异常类,或者至少

Now, finally, to the gist of the question: should one now best derive a unique exception class for every possible failure cause, or at least for major failure causes?

我说不:不要添加不必要的复杂性

如果在什么地方或什么地方可以使调用者区分某个失败原因,那么使用一个独特的异常类非常有用。但是在大多数情况下,调用者唯一感兴趣的信息是发生异常的唯一事实。

If or where it is can be useful for a caller to distinguish a certain failure cause, a distinct exception class for that is very useful. But in most cases the only information of interest to a caller is the single fact that an exception has occurred. It is very rare that different failure causes lead to different attempted fixes.

但是失败原因代码呢?

好吧,当这就是基础API所提供的功能时,只需添加工作即可创建相应的异常类。但是另一方面,当您在呼叫链中传达故障时,呼叫者可能需要知道确切的原因,然后使用该代码,这意味着呼叫者将不得不在<$内使用一些嵌套的检查和调度。 c $ c>捕获。因此,这是不同的情况:(A)您的代码是失败指示的原始来源,而(B)您的代码使用例如Windows或Posix API函数失败,并通过失败原因代码指示失败原因。

Well, when that's what an underlying API gives you, it is just added work to create corresponding exception classes. But on the other hand, when you are communicating failure up in a call chain, and the caller might need to know the exact cause, then using a code for that means the caller will have to use some nested checking and dispatch inside the catch. So these are different situations: (A) your code is the original source of a failure indication, versus (B) your code uses e.g. a Windows or Posix API function that fails and that that indicates failure cause via a failure cause code.

这篇关于如何设计异常“类型”?在C ++中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆