【问题标题】:Exception vs. error-code vs. assert异常与错误代码与断言
【发布时间】:2010-11-26 04:15:16
【问题描述】:

我正在开发一个生成设备报告的库。 generate_report (const std::string& no) 成员函数可能由于各种原因而失败:

  1. 报告编号无效。
  2. 无效状态(report_generator 是 FSM)
  3. 没有设备处于活动状态
  4. 报告生成期间出错

哪种错误处理机制最适合这些错误?

  • 只需返回truefalse
  • 返回错误代码
  • 断言并记录
  • 抛出异常
  • 以上任意组合

一些上下文信息:正常的工作流程如下。用户激活设备,从列表中选择报告并点击“生成”。

编辑:感谢到目前为止的回复!对我来说,现在很清楚何时使用断言以及何时进行错误处理。至于错误处理,错误代码和异常各有利弊。我想我会寻找例外(并为上述错误创建四个类),但我还没有真正相信。我总是想到“意外情况”的例外情况。一个无效的报告不是真的出乎意料。有什么建议吗? :)

【问题讨论】:

标签: c++ exception error-handling assert


【解决方案1】:

其中任何一个都有不同的目的:

  • 错误代码版本。异常:异常和错误代码代表了如何处理结果代码的不同习惯用法。异常更加健壮 - 结果代码可以被忽略或丢失。一个库通常应该强烈区分抛出哪里/什么异常,以及何时使用错误代码。充其量只使用两者之一。

  • return truefalse:错误代码的特殊化。 通常是最糟糕的想法 - 只有在报告好或坏的情况下才是好(即malloc 返回好或坏 (= NULL)。

  • assert 和 log:这些是调试技术,不应用作向用户/客户端报告的机制。断言只是说“发生了一些我无法处理的事情 - 我退出了”。

【讨论】:

  • assert(on false)的行为不一定要退出。也可以使用源文件名、行号、堆栈转储和断言文本(可能包含某些变量的值)等信息记录到日志文件。另一种行为是使用 Visual Studio 创建的 Windows 应用程序的默认行为:显示一个对话框,其中包含用于中止、重试或忽略的按钮。
  • 是的,断言不必退出 - 但这是典型的情况。我使用自己的断言来提供您提到的可能性(继续、退出等)。根据我的经验,虽然在大多数情况下没有安全的出路,因为错误会不断重复。
【解决方案2】:

我将违背常规并建议错误代码和异常,但这只是因为您正在创建一个库。既然您说您正在创建一个库,我猜想该库将可供您无法控制的人编写的代码使用。所以,让你的代码对不同的编译器甚至语言都友好是一件好事。

所以我会编写一个 C++ 异常库并提供详细说明异常类的头文件。我还将编写一个为用户处理异常的 C 接口。现在用户可以链接到哪个界面是合适的:

#ifdef __cplusplus__
void generate_report(const std::string& rep_number, ostream& output);

extern "C" 
#endif
int generate_report(const char* rep_number, const char* outputfilename,
                    int* error_code, char* error_text, int max_error_text_len);

C 实现调用 C++ 实现:

extern "C" 
int generate_report(const char* rep_number, const char* outputfilename,
                    int* error_code, char* error_text, int max_error_text_len)
{
    ofstream os;
    try {
        os.open(outputfilename, IOS_WRITE);
        generate_report(rep_number, os);
        os.close();
        return TRUE;
    } catch (base_exception& e) {
        os.close();
        if (error_code) *error_code = e.error_code();
        if (error_text) strncpy(error_text, e.str(), max_error_text_len);
        return FALSE;
    }
}

【讨论】:

    【解决方案3】:

    断言不是正确的选择。当你有一个不变量时使用断言;不应该发生的事情。不要做诸如 assert() 之类的事情,如果参数是错误条件而不是不变量,则参数永远不会为空。

    如果是我,我会在接口中使用异常,如果必须的话,如果内部使用的函数不使用异常,我会翻译错误代码。保持一致(不要对这些东西使用断言)。

    【讨论】:

    • new 返回的指针不会为空,除非你使用“new (nothrow)”。
    • 你确定吗?我知道“标准”规定 new 如果失败会抛出异常,但是有很多遗留代码是在制定该规则之前编写的。无论如何,在这种情况下,这可能是一个令人困惑的例子,因为它不适用于大多数情况。
    • @Ed 是的,我确定。除非您使用显示“new never throws”的特定于供应商的编译器标志,否则检查 new 是否返回 null 的遗留代码将在 new 失败时实际抛出(顺便说一句,对于普通新闻,它是“never”)。哈弗,我同意你的主要观点。
    • 是的,“你确定吗”是错误的,因为在任何现代编译器上,new 都会抛出而不是返回 null。改变了例子。
    【解决方案4】:
    • 如果您无权访问终端以生成/读取错误报告,则应使用日志记录。
    • 返回 True/False 应与错误代码结合使用。示例:函数在成功时返回 True,在错误时返回 False,并使用适当的错误代码/描述设置变量(全局或参数,您的选择)。
    • 异常:在我看来,最好将它们与日志记录和从错误中优雅恢复结合起来。如果这不可能,您不妨求助于错误代码,因为异常不会提供额外的好处。
    • assert():正如其他人指出的那样,它会在发布版本上编译掉,所以可以随意触发。

    2c

    【讨论】:

    • 呃,assert() 不会在 Release 版本中编译掉吗?
    • 您可能想要查找文档。 中的 assert() 在发布版本中会编译掉。
    【解决方案5】:

    与真/假和错误代码相比,异常有几个重要的优势:

    • 不能忽略异常。如果您的代码抛出异常,调用者必须捕获它以避免出现未处理的异常。
    • 可以在比直接调用者更高的级别处理异常。如果您使用错误代码,您最终可能会遇到这样的情况:您的应用程序的所有层都必须检查错误并将其传回给调用者。

    断言用于表达代码中的前置条件等内容,并有望在开发过程中发现任何错误。但是,您不应依赖发布代码中的断言,并且出于性能原因,断言通常会从发布代码中删除。

    【讨论】:

    • C++ 中异常的危险在于,您可能会留下一个释放内存的堆栈帧,从而导致难以跟踪的内存泄漏。
    【解决方案6】:

    您报告的设备的可靠性如何?

    我问是因为对于一大类未连接、未打开、电池耗尽、忙于做其他事情等的设备都是相当正常的状态。

    如果是这种情况,如果设备不可用,我会倾向于返回状态代码(注意不是错误代码)。

    另一方面,如果您认为这些设备非常可靠,并且它们不响应确实是例外,那么异常处理可能是要走的路。

    mutch 并不重要,因为“异常”实际上只是一种奇特的方式来编写 'if (x != 0) { goto error_routine; },但是,我个人更喜欢异常处理来处理异常情况,而不是像 end_of_file 这样的例行事件。

    【讨论】:

    • 好点。我认为问题是什么是特殊情况。例如。如果我想生成不存在的 42 号报告,或者在未选择设备时生成报告。
    【解决方案7】:

    我建议阅读 Boost 社区 guide [boost.org] 以了解异常和错误处理。

    【讨论】:

      【解决方案8】:

      首先 - 保持一致!

      第二:

      • 只有真/假是不够的。它必须与错误代码相结合(例如 false + getLastError)。
      • 错误代码很快,但构建一些基础设施可以轻松地将它们转换为字符串。
      • assert/log:不,您希望应用程序能够对错误做出反应
      • 异常比错误代码慢,但更容易通过复杂的控制流程进行编程。
      • 组合:只有真/假+错误代码组合,其余的保持一致,这意味着:不要组合。

      【讨论】:

      • “异常比错误代码慢”需要引用
      • @BigTemp pspdfkit.com/blog/2020/…。也很清楚为什么必须是这种情况(至少对于 C++)——它们需要动态内存分配。性能问题及其不确定性是它们在某些行业(游戏、嵌入式......)中被禁用的原因。如需更多信息,请查看open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0709r1.pdf(Herb Sutter 的 Herbception 论文)。
      【解决方案9】:

      选择什么策略通常是个人喜好问题。我说选择最能与您图书馆的客户集成的东西。如果他们采用异常策略,请使用异常。如果他们习惯了错误代码,请坚持下去。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-09-29
        • 1970-01-01
        • 1970-01-01
        • 2010-10-24
        • 2021-11-01
        • 2018-09-02
        • 1970-01-01
        相关资源
        最近更新 更多