C ++异常类设计

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

我看到了关于哪些异常类应该和不应该做什么的各种内容,但是没有一个简单的设计可以轻松地使用和扩展这些功能。

  1. 异常类不应引发异常,因为这可能直接导致进程终止,而没有机会记录错误等。
  2. 必须有可能获得用户友好的字符串,最好是本地化为其语言的字符串,以便在无法从错误中恢复之前,在应用程序终止自身之前有一些要告诉他们的内容。
  3. 可能需要在堆栈展开时添加信息,例如,如果XML解析器无法解析输入流,能够添加源是来自文件还是通过网络等。
  4. 异常处理程序需要轻松访问处理异常所需的信息。
  5. 将格式化的异常信息写入日志文件(英语,因此此处无翻译)。

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

编辑:因此,在几个类别中查看了异常类以及与Neil关联的问题之后,似乎完全不考虑项目1(因此忽略了提升建议)似乎是一种惯例,这对我来说似乎是个坏主意。

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

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];
};

由于所有字符串都通过复制到内部固定大小的缓冲区中进行处理(如果需要则截断,但始终以null终止)来处理点1。

尽管这没有解决第3点,但是我认为无论如何在现实世界中该点的使用都很有限,如果有必要,可以通过抛出新的异常来解决。

Fire Lancer asked 2020-08-12T00:12:27Z
6个解决方案
24 votes

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

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

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

我不会做太多的异常类。 您可以将有关异常的很多详细信息打包到类中,因此您不必为每种错误都制作一个唯一的异常类。 另一方面,您确实希望使用唯一的类来处理预期要处理的错误。 如果要进行解析器,则可能只有一个语法异常错误,其成员描述问题的详细信息,而不是一堆针对不同类型语法错误的特殊异常。

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

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

C ++中的异常应针对“例外”条件。 因此,解析示例可能不是一个好例子。 解析文件时遇到的语法错误可能不够特殊,无法保证由异常处理。 我要说的是,除非程序没有明确地处理条件,否则程序可能无法继续运行,这是一种例外。 因此,大多数内存分配失败都是例外,但用户输入的错误可能不是这种情况。

Adrian McCarthy answered 2020-08-12T00:13:03Z
7 votes

使用虚拟继承。 这种见解归功于Andrew Koenig。 如果有人抛出从多个具有共同基类的基类派生的异常,则从异常的基类使用虚拟继承可防止捕获现场出现歧义问题。

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

jon-hanson answered 2020-08-12T00:13:27Z
7 votes


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

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

grimner answered 2020-08-12T00:14:02Z
4 votes

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

这避免了与管理所抛出异常的内存(如果您抛出了指针)以及可能进行对象切片(如果您按值捕获异常)有关的问题。

Michael Burr answered 2020-08-12T00:14:26Z
3 votes

由于C ++ 11可以使用std::nested_exceptionstd::throw_with_nested,因此我想在此处和此处指向StackOverflow的答案

这些答案描述了如何通过简单地编写将抛出嵌套异常的适当异常处理程序,而无需调试程序或繁琐的日志记录就可以对代码中的异常进行追溯。

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

GPMueller answered 2020-08-12T00:14:57Z
2 votes

一个好的设计是不要创建一组异常类-只需根据std :: exception为每个库创建一个。

添加信息非常容易:

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

而且异常处理程序具有所需的信息,因为它们是异常处理程序-只有try块中的函数会导致异常,因此处理程序仅需要考虑这些错误。 而且不是,您不应该真正在常规错误处理中使用异常。

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

我想这是以前问过的,但是我现在找不到。

answered 2020-08-12T00:15:35Z
translate from https://stackoverflow.com:/questions/1335561/c-exception-class-design