C++异常类设计

Fir*_*cer 42 c++ exception-handling exception

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

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

  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];
};
Run Code Online (Sandbox Code Playgroud)

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

虽然这不涉及第3点,但我认为这一点很可能在现实世界中有限使用,并且很可能通过在需要时抛出新的异常来解决.

Adr*_*thy 24

使用浅层次的异常类.使层次结构过于深入会增加复杂性而不是价值.

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

如果这是针对特定库或模块的,则可能需要特定于模块的基础(仍然来自其中一个标准异常类).呼叫者可能决定以这种方式从您的模块中捕获任何内容.

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

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

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

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

  • 处理异常的catch块应该从资源加载UI字符串.类似于:`catch(const syntax_error&ex){std :: cerr << ex.filename()<<"("<< ex.line_number()<<"):"<< GetText(IDS_SYNTAXERROR)<< std: :ENDL; ``,其中`GetText()`从程序的资源中加载一个字符串. (5认同)

jon*_*son 7

使用虚拟继承.这种见解归功于Andrew Koenig.使用来自异常基类的虚拟继承可防止捕获站点出现歧义问题,以防有人抛出从具有共同基类的多个基础派生的异常.

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

  • 显然有人写应用程序而不是库.该结构是合法的C++,因此它将被使用,无论事实是"没有任何理由将其用于异常类".如果不是道歉,你欠Jon一个upvote - pgast 0秒前 (5认同)
  • 首先不从多个基础派生异常怎么样?一般来说,我不介意 MI,但我看不出有任何理由将它用于异常类。 (2认同)

gri*_*ner 7


2:不,您不应该将用户界面(=本地化消息)与程序逻辑混合在一起.当应用程序意识到它无法处理问题时,应在外层完成与用户的通信.异常中的大多数信息都是过多的实现细节,无论如何都无法向用户显示.
3:对此
5 使用boost.exception :不要这样做.请参阅2.记录的决定应始终位于错误处理站点.

不要只使用一种类型的例外.使用足够的类型,以便应用程序可以为所需的每种类型的错误恢复使用单独的catch处理程序


小智 2

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

添加信息相当简单:

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

异常处理程序拥有它们需要的信息,因为它们是异常处理程序——只有 try 块中的函数才能引发异常,因此处理程序只需要考虑那些错误。而不是你真的不应该使用异常来进行一般错误处理。

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

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

  • 分不同的班级有什么问题吗?您可能想要以不同的方式处理它们,甚至处理一些而留下其他。 (8认同)
  • 此代码示例有缺陷。`std::exception::what()` 返回 `const char *`,因此 `ex.what() + " more text "` 不会像与字符串对象一样连接文本。您不希望异常类中存在真正的字符串对象,因为它们可能会导致嵌套异常。 (8认同)
  • 但是拥有不同类型的异常的目的是能够指定您要传输到的上下文!即类型 A 的异常会立即被捕获,但类型 B 的异常会在堆栈中向上传播。 (5认同)
  • 标准::运行时错误:19.1.6。runtime_error 有两个构造函数。一个使用 std::string,另一个使用 C-String。使用这些构造函数的后置条件表明 What() 将返回与输入匹配的 C 字符串。参见注释:(3) 和(5)。因此,该标准没有规定如何实现结果,它只是指定结果应该是什么(这取决于实现如何实现)。注意:std::runtime_error继承自std::exception。 (5认同)
  • 我更喜欢从 std::runtime_error 而不是 std::exception 派生异常。std::runtime_error 已经具有内置的消息处理功能,而 std::exception 则没有。 (3认同)
  • @Neil - 类runtime_error派生自std::exception(19.1.6),因此它获得继承的what()方法 (2认同)
  • 它不是实现定义的 runtime_error::what() 将返回的内容。19.1.6 说构造函数有这个后置条件:strcmp(what(), What_arg.c_str()) == 0 (2认同)