【问题标题】:C++ exception class design [closed]C ++异常类设计
【发布时间】:2009-08-26 15:27:45
【问题描述】:

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

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

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

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

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

【问题讨论】:

    标签: c++ exception exception-handling


    【解决方案1】:

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

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

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

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

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

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

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

    【讨论】:

    • "异常中的字符串用于调试。您不应该在用户界面中使用它们。您希望将 UI 和逻辑尽可能分开,以启用诸如翻译到其他语言之类的功能。”所以应该在哪里以及如何生成这样的字符串,正如你所说的,exceptiosn是导致程序退出的有用的东西,或者至少不能做用户想做的事情,所以需要有一个最终用户友好的纹理表示对于绝大多数例外情况。
    • 处理异常的 catch 块应该从资源中加载 UI 字符串。类似于:catch (const syntax_error &ex) { std::cerr << ex.filename() << "(" << ex.line_number() << "): " << GetText(IDS_SYNTAXERROR) << std::endl; },其中GetText() 从您的程序资源中加载一个字符串。
    • C++11 或 C++17 会改变你的答案吗?不过,我对 C++11 比对 C++17 更感兴趣。
    • @Gizmo:我仍在学习 C++17 中的内容,但 C++11 中的任何内容都不会促使我更改我在这里所说的任何内容。 constexpr 或异常类中的移动构造函数和移动赋值可能有一些用途,以避免在传播异常时遇到其他问题,但如果你真的需要这些,我想知道你的异常类是否太复杂。
    【解决方案2】:

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

    boost site 上的其他同样有用的建议

    【讨论】:

    • 一开始就不从多个碱基派生异常怎么样?我一般不介意 MI,但我看不出有任何理由将它用于异常类。
    • 一个异常可能是由不止一种类型的问题引起的,是不是不可思议?
    • 显然有人编写应用程序而不是库。该结构是合法的 C++ 结构,因此无论“没有任何理由将其用于异常类”这一事实都将使用它。如果不是道歉,你欠乔恩一个赞成票 – pgast 0 秒前
    【解决方案3】:


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

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

    【讨论】:

      【解决方案4】:

      与异常类层次结构的设计没有直接关系,但重要(并且与使用这些异常有关)是您通常应该throw by value and catch by reference.

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

      【讨论】:

        【解决方案5】:

        由于 std::nested_exceptionstd::throw_with_nested 已随 C++11 提供,我想指出 StackOverflow herehere 上的答案

        这些答案描述了如何在代码中获取异常的回溯,而无需调试器或繁琐的日志记录,只需编写一个将重新引发嵌套异常的适当异常处理程序。

        在我看来,那里的异常设计还建议不要创建异常类层次结构,而是每个库只创建一个异常类(正如an answer to this question 中已经指出的那样)。

        【讨论】:

          【解决方案6】:

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

          添加信息相当容易:

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

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

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

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

          【讨论】:

          • 拥有不同的类有什么问题?您可能希望以不同的方式处理它们,甚至处理一些并留下其他。
          • 但是拥有不同类型的异常的关键是能够指定你转移到的上下文! IE。 A 类型的异常会立即被捕获,但 B 类型的异常会在堆栈中向上传播。
          • 我更喜欢从 std::runtime_error 而不是 std::exception 派生我的异常。 std::runtime_error 已经内置了 std::exception 没有的消息处理功能。
          • 这个代码示例有缺陷。 std::exception::what() 返回const char *,因此ex.what() + " more text " 不会像与字符串对象那样连接文本。您不希望异常类中包含真正的字符串对象,因为它们可能导致嵌套异常。
          • 标准::runtime_error: 19.1.6。 runtime_error 有两个构造函数。一个带有 std::string 另一个带有 C-String。使用这些构造函数的后置条件表明 what() 将返回与输入匹配的 C-String。见注:(3)和(5)。所以标准并没有规定如何实现结果,它只是指定了结果应该是什么(这取决于实现如何实现)。注意:std::runtime_error 继承自 std::exception。
          猜你喜欢
          • 1970-01-01
          • 2013-02-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-07-02
          • 2021-08-13
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多