C++异常类设计

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1335561/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 19:36:14  来源:igfitidea点击:

C++ exception class design

c++exceptionexception-handling

提问by Fire Lancer

What is a good design for a set of exception classes?

什么是一组异常类的好设计?

I see all sorts of stuff around about what exception classes should and shouldn't do, but not a simple design which is easy to use and extend that does those things.

我看到了各种各样的关于异常类应该做什么和不应该做什么的东西,但不是一个易于使用和扩展的简单设计来做这些事情。

  1. The exception classes shouldn't throw exceptions, since this could lead straight to the termination of the process without any chance to log the error, etc.
  2. It needs to be possible to get a user friendly string, preferable localised to their language, so that there's something to tell them before the application terminates itself if it can't recover from an error.
  3. It needs to be possible to add information as the stack unwinds, for example, if an XML parser fails to parse an input stream, to be able to add that the source was from a file, or over the network, etc.
  4. Exception handlers need easy access to the information they need to handle the exception.
  5. Write formatted exception information to a log file (in English, so no translations here).
  1. 异常类不应该抛出异常,因为这可能会直接导致进程终止而没有机会记录错误等。
  2. 需要有可能获得用户友好的字符串,最好对其语言进行本地化,以便在应用程序无法从错误中恢复而自行终止之前可以告诉他们一些信息。
  3. 需要能够在堆栈展开时添加信息,例如,如果 XML 解析器无法解析输入流,则能够添加源来自文件或通过网络等。
  4. 异常处理程序需要轻松访问处理异常所需的信息。
  5. 将格式化的异常信息写入日志文件(英文,所以这里没有翻译)。

Getting 1 and 4 to work together is the biggest issue I'm having, since any formatting and file output methods could potentially fail.

让 1 和 4 一起工作是我遇到的最大问题,因为任何格式和文件输出方法都可能失败。

EDIT: So having looked at exception classes in several classes, and also in the question Neil linked to, it seems to be common practice to just completely ignore item 1 (and thus the boost recommendations), which seems to be a rather bad idea to me.

编辑:因此,在查看了几个类中的异常类以及 Neil 链接到的问题中,完全忽略第 1 项(以及提升建议)似乎是一种常见做法,这似乎是一个相当糟糕的主意我。

Anyway, I thought I'd also post the exception class I'm thinking of using.

无论如何,我想我也会发布我正在考虑使用的异常类。

class Exception : public std::exception
{
    public:
        // Enum for each exception type, which can also be used
        // to determine the exception class, useful for logging
        // or other localisation methods for generating a
        // message of some sort.
        enum ExceptionType
        {
            // Shouldn't ever be thrown
            UNKNOWN_EXCEPTION = 0,

            // The same as above, but it has a string that
            // may provide some information
            UNKNOWN_EXCEPTION_STR,

            // For example, file not found
            FILE_OPEN_ERROR,

            // Lexical cast type error
            TYPE_PARSE_ERROR,

            // NOTE: in many cases functions only check and
            //       throw this in debug
            INVALID_ARG,

            // An error occured while trying to parse
            // data from a file
            FILE_PARSE_ERROR,
        }

        virtual ExceptionType getExceptionType()const throw()
        {
            return UNKNOWN_EXCEPTION;
        }

        virtual const char* what()throw(){return "UNKNOWN_EXCEPTION";}
};


class FileOpenError : public Exception
{
    public:
        enum Reason
        {
            FILE_NOT_FOUND,
            LOCKED,
            DOES_NOT_EXIST,
            ACCESS_DENIED
        };
        FileOpenError(Reason reason, const char *file, const char *dir)throw();
        Reason getReason()const throw();
        const char* getFile()const throw();
        const char* getDir ()const throw();

    private:
        Reason reason;
        static const unsigned FILE_LEN = 256;
        static const unsigned DIR_LEN  = 256;
        char file[FILE_LEN], dir[DIR_LEN];
};

Point 1 is addressed since all strings are handled by copying to an internal, fixed size buffer (truncating if needed, but always null terminated).

第 1 点已解决,因为所有字符串都是通过复制到内部固定大小的缓冲区来处理的(如果需要,可以截断,但始终以 null 结尾)。

Although that doesn't address point 3, however I think that point is most likely of limited use in the real world anyway, and could most likely be addressed by throwing a new exception if needed.

虽然这并没有解决第 3 点,但是我认为无论如何,这点在现实世界中的用途很可能有限,并且很可能可以通过在需要时抛出新异常来解决。

回答by Adrian McCarthy

Use a shallow hierarchy of exception classes. Making the hierarchy too deep adds more complexity than value.

使用异常类的浅层次结构。使层次结构太深会增加复杂性而不是价值。

Derive your exception classes from std::exception (or one of the other standard exceptions like std::runtime_error). This allows generic exception handlers at the top level to deal with any exceptions you don't. For example, there might be an exception handler that logs errors.

从 std::exception(或其他标准异常之一,如 std::runtime_error)派生您的异常类。这允许顶层的通用异常处理程序处理您不处理的任何异常。例如,可能有一个记录错误的异常处理程序。

If this is for a particular library or module, you might want a base specific to your module (still derived from one of the standard exception classes). Callers might decide to catch anything from your module this way.

如果这是针对特定库或模块的,您可能需要一个特定于您的模块的基类(仍然派生自标准异常类之一)。调用者可能会决定以这种方式从您的模块中捕获任何内容。

I wouldn't make too many exception classes. You can pack a lot of detail about the exception into the class, so you don't necessarily need to make a unique exception class for each kind of error. On the other hand, you do want unique classes for errors you expect to handle. If you're making a parser, you might have a single syntax_error exception with members that describe the details of the problem rather than a bunch of specialty ones for different types of syntax errors.

我不会做太多的异常类。您可以将大量有关异常的详细信息打包到类中,因此您不必为每种错误都创建一个唯一的异常类。另一方面,对于希望处理的错误,您确实需要唯一的类。如果你正在做一个解析器,你可能有一个单一的syntax_error 异常,它的成员描述了问题的细节,而不是一堆针对不同类型语法错误的特殊异常。

The strings in the exceptions are there for debugging. You shouldn't use them in the user interface. You want to keep UI and logic as separate as possible, to enable things like translation to other languages.

异常中的字符串用于调试。您不应该在用户界面中使用它们。您希望尽可能将 UI 和逻辑分开,以实现诸如翻译成其他语言之类的功能。

Your exception classes can have extra fields with details about the problem. For example, a syntax_error exception could have the source file name, line number, etc. As much as possible, stick to basic types for these fields to reduce the chance of constructing or copying the exception to trigger another exception. For example, if you have to store a file name in the exception, you might want a plain character array of fixed length, rather than a std::string. Typical implementations of std::exception dynamically allocate the reason string using malloc. If the malloc fails, they will sacrifice the reason string rather than throw a nested exception or crashing.

您的异常类可以有额外的字段,其中包含有关问题的详细信息。例如,syntax_error 异常可以有源文件名、行号等。尽可能坚持这些字段的基本类型,以减少构造或复制异常以触发另一个异常的机会。例如,如果必须在异常中存储文件名,则可能需要固定长度的纯字符数组,而不是 std::string。std::exception 的典型实现使用 malloc 动态分配原因字符串。如果 malloc 失败,他们将牺牲原因字符串而不是抛出嵌套异常或崩溃。

Exceptions in C++ should be for "exceptional" conditions. So the parsing examples might not be good ones. A syntax error encountered while parsing a file might not be special enough to warrant being handled by exceptions. I'd say something is exceptional if the program probably cannot continue unless the condition is explicitly handled. Thus, most memory allocation failures are exceptional, but bad input from a user probably isn't.

C++ 中的异常应该是针对“异常”条件的。所以解析示例可能不是很好的示例。解析文件时遇到的语法错误可能不够特殊,不足以保证由异常处理。如果除非明确处理条件,否则程序可能无法继续,我会说有些事情是例外的。因此,大多数内存分配失败都是例外的,但来自用户的错误输入可能不是。

回答by jon-hanson

Use virtual inheritance. This insight is due to Andrew Koenig. Using virtual inheritance from your exception's base class(es) prevents ambiguity problems at the catch-site in case someone throws an exception derived from multiple bases which have a base class in common.

使用虚拟继承。这种洞察力归功于安德鲁·科尼格 (Andrew Koenig)。使用来自异常基类的虚拟继承可以防止在捕获站点出现歧义问题,以防有人抛出从具有共同基类的多个基派生的异常。

Other equally useful advice on the boost site

boost 网站上的其他同样有用的建议

回答by grimner


2: No you should not mix user interface (=localized messages) with program logic. Communication to the user should be done at an outer level when the application realises that it cannot handle the issue. Most of the information in an exception is too much of an implementation detail to show a user anyway.
3: Use boost.exception for this
5: No dont do this. See 2. The decision to log should always be at the error handling site.


2:不,您不应将用户界面(=本地化消息)与程序逻辑混合使用。当应用程序意识到它无法处理问题时,应该在外部级别与用户进行通信。异常中的大多数信息都包含过多的实现细节,无法向用户显示。
3:为此使用boost.exception
5:不,不要这样做。请参阅 2。记录的决定应始终在错误处理站点。

Dont use only one type of exception. Use enough types so the application can use a separate catch handler for each type of error recovery needed

不要只使用一种类型的异常。使用足够的类型,以便应用程序可以为所需的每种类型的错误恢复使用单独的捕获处理程序

回答by Michael Burr

Not directly related to the design of an exception class hierarchy, but important (and related to using those exceptions) is that you should generally throw by value and catch by reference.

与异常类层次结构的设计没有直接关系,但重要的(与使用这些异常相关)是您通常应该按值抛出并按引用捕获。

This avoids problems related to managing the memory of the thrown exception (if you threw pointers) and with the potential for object slicing (if you catch exceptions by value).

这避免了与管理抛出异常的内存相关的问题(如果你抛出了指针)和潜在的对象切片(如果你按值捕获异常)。

回答by GPMueller

Since std::nested_exceptionand std::throw_with_nestedhave become available with C++11, I would like to point to answers on StackOverflow hereand here

由于std::nested_exceptionstd::throw_with_nested已变得可利用C ++ 11,我想点在计算器上答案在这里这里

Those answers describe how you can get a backtrace on your exceptionsinside your code without need for a debugger or cumbersome logging, by simply writing a proper exception handler which will rethrow nested exceptions.

这些答案描述了如何不需要调试器或繁琐的日志记录的情况下获取代码中异常的回溯,只需编写一个适当的异常处理程序即可重新抛出嵌套异常。

The exception design there, in my opinion, also suggests to not create exception class hierarchies, but to only create a single exception class per library(as already pointed out in an answer to this question).

在我看来,那里的异常设计还建议不要创建异常类层次结构,而是只为每个库创建一个异常类(正如在这个问题的回答中已经指出的那样)。

回答by GPMueller

A good design is not to create a set of exception classes - just create one per library, based on std::exception.

一个好的设计不是创建一组异常类——只需基于 std::exception 为每个库创建一个。

Adding information is fairly easy:

添加信息相当容易:

try {
  ...
}
catch( const MyEx & ex ) {
   throw MyEx( ex.what() + " more local info here" );
}

And exception handlers have the information they need because they are exception handlers - only the functions in the try blocks can cause exceptions, so the handlers only need to consider those errors. And not you should not really be using exceptions for general error handling.

并且异常处理程序有他们需要的信息,因为它们是异常处理程序——只有 try 块中的函数会导致异常,所以处理程序只需要考虑那些错误。并且您不应该真正使用异常进行一般错误处理。

Basically, exceptions should be as simple as possible - a bit like logfiles, which which they should have no direct connection.

基本上,异常应该尽可能简单——有点像日志文件,它们应该没有直接的联系。

This has been asked before, I think, but I can't find it right now.

以前有人问过这个问题,我想,但我现在找不到。