【问题标题】:Classes that don't throw but depend on libraries that do不抛出但依赖于抛出的类
【发布时间】:2014-04-20 18:53:57
【问题描述】:

我编写了一些不抛出异常的类,但它们使用 STL,而 STL 可以抛出异常。例如,在我的课堂上有使用 std::vector、std::list、std::string 的函数。 STL 可能会在复制字符串或创建向量时抛出,对吗?所以我不能将我的课程描述为无异常,对吗?

在这种情况下,你们会怎么做?您是否将每个函数都包装在 try/catch 中?你如何描述你的班级?谢谢

【问题讨论】:

  • @cmaster 你的推理有好处,但你的结论是荒谬的。您如何解释现代语言已删除 goto仍有例外这一事实?
  • @djechlin 这对我来说仍然是个谜。我只能得出结论,人们只是没有意识到。多年来,我本人从未质疑例外概念的有效性。你说,我的结论很荒谬,因为大多数程序员都认为异常很棒。但是大多数人都完全错了,它可能会再次发生。一方面,我会坚持让我相信的原因:例外情况比goto 更糟糕。所以,要么goto 是无害的,要么例外是有害的。你会选择哪一个?
  • 许多语言都遵循各种“就是这样”的设计方法,没有任何批判性思维或深入检查此类决策的利弊。其他语言也不是 C++,对 Ruby、JavaScript 或 C# 有意义的可能 对 C++ 没有神奇的自动意义。我会想象课后特价会告诉人们“其他人都这样做,所以它一定很好”是一种糟糕的思考方式,但显然不是。 :)
  • @cmaster:异常不适用于跳过代码。当出现不常见的错误时,它们的目的是做一些比std::abort() 的默认行为更有用的事情。这个有用的东西通常需要与代码在常见情况下要做的事情有很大不同,当然。
  • @djechlin:“异常与错误返回”是错误的双头垄断。您编写的任何异常安全代码要么是微不足道的,要么很可能实际上不是异常安全的。更不用说许多非常有用的算法和容器实际上不可能使异常安全。 C++ 社区中有很大一部分会禁用异常、避免使用 STL、禁止新建和删除以及使用无异常的中间件。很多聪明人肯定不同意你的观点,所以根据你自己的推理...... :)

标签: c++ stl


【解决方案1】:

正确,如果您从特定成员函数调用的任何内容(包括构造函数、编译器为您提供的隐式调用等)都可以引发异常,则该成员函数可以引发异常。所以这不是例外。

至于如何处理:这实际上取决于您的代码应该做什么,以及“如果它引发异常,您可以做什么”。您可能想在某处抓住它,但由于最有可能的情况是您做了一些愚蠢的事情和/或内存不足,您很可能无法真正应对这种情况。 (当然,如果您使用例如 std::vector::at() 的值超出范围,那么它会抛出异常 - 那是“做一些愚蠢的事情” - 就像我做过几次的那样,const char* p = 0; .... std:string str(p); - 它当然可能会崩溃而不是抛出异常,但我的编译器似乎从中抛出了 bad_something 异常)。这些事情中的任何一件,如果它们不是有意的,都可能是“无论如何你的代码已经死亡”。如果您使用带有错误索引的 std::vector::at() 并且您“有意这样做”,那么您可能应该重新考虑您的设计 - 与 if (vec.size() > index) ... else ... 相比,异常是“昂贵的”

我不确定是否有“我的类不会抛出异常,但使用可能会抛出异常的标准库”的特定术语。

【讨论】:

    【解决方案2】:

    幸运的是,由于分配问题而抛出 STL 类的可能性非常小:您的进程更有可能被 OOM-killer 击落,而不是分配失败。所以我倾向于忽略这些类型的异常,只是让它们使程序崩溃。我对不使用异常非常激进,所以我写的代码中没有任何try{}catch(){}

    当我不是一个人在做一个项目时,我也不会尝试包装每个函数调用,我只是将我的函数声明为throw()。这样做的效果是,我调用的任何其他函数产生的任何意外异常都会安全地使程序崩溃,而不是传播到可能捕获它的其他函数。这样我就可以确定我的代码的任何重要部分都不会在没有人注意到的情况下被跳过。

    【讨论】:

    • 如果您检测到错误(即意外异常),您应该使 整个 应用程序崩溃,这很荒谬。例如,如果您正在编写一个日志库,它需要完全确保它永远不会干扰程序的行为,并且吞下错误总比失败要好得多。这适用于任何“mixin”类型的行为。例如。进行只读数据跟踪和分析的模块在失败时不应使整个 Web 应用程序崩溃。
    • @djechlin 我同意你的观点,一些服务提供商不应该让整个应用程序崩溃。我更喜欢立即吵闹地崩溃是启用调试。任何我在编写代码时没有预料到的异常都是一个错误:我绝对必须正确处理它。崩溃允许我把东西转储到调试器中并检查情况。如果我只是让它在不注意的情况下传递我的代码,它可能会使程序处于不一致的状态(因为我没想到它!),并且您知道由于数据损坏而调试问题是多么困难。
    • false - 要么该异常将在上面更一般的异常块中捕获并正确处理,要么它无论如何都会逃脱,因为它从未被捕获并无论如何都会使程序崩溃。通常,顶级电源将具有类似的东西,例如在 Java 中:catch(Exception exc) { System.err.println("Exception: " + exc.getMessage()); exc.printStrackTrace(); System.exit(1); }
    【解决方案3】:

    请参阅 GotW #82 (http://gotw.ca/gotw/082.htm) 添加异常规范总是一个坏主意:

    异常规范可能会导致令人惊讶的性能下降,例如 例如,如果编译器关闭函数的内联 异常规范。运行时意外()错误并不总是 对于异常的错误种类,您希望发生什么 规范旨在捕捉。你一般不会写有用的 无论如何,函数模板的异常规范,因为你 通常无法判断他们操作的类型可能会抛出什么。

    【讨论】:

      【解决方案4】:

      你的设计有缺陷。

      我写了一些不抛出异常的类,但是它们使用了 STL,而 STL 可以抛出异常。

      您需要回答这个问题 - 如果您的程序因 STL 失败而失败,您会怎么做?您唯一的选择是抛出异常或返回值。 (或者当场使应用程序崩溃。)当然,您可以选择返回一个您编写的自定义类SuccessOrFailure。成功时,它将包含实际的返回数据,而失败时,它将包含一些描述问题所在的信息。换句话说,您可以将本应抛出的异常塞入返回值,并强制客户端代码检查返回值是否有错误,并执行抛出异常时会执行的操作,只编写 C 样式的返回值代码是在发明异常之前是 C 编程的祸根之一。但如果这是您的设计决定,那就是您必须做的。

      或者您可以尝试返回一些表示“失败”的值,例如,对于整数,返回 0 或 -1。当然,这种解决方案只有在您的实际函数清楚地只返回正数时才有效,并且如果您使用固定数字,它不会显示任何有关错误的信息。

      我的意思是,无论你是否抛出异常,你都需要弄清楚如何处理失败。“不使用异常”不能回答这个问题,它只是忽略了工作。

      我见过的唯一个不使用异常的正当理由是

      • 您正在维护旧的面向返回值的代码,在这种情况下,您需要做一些敷衍的工作,将您的异常转换为不同的返回代码
      • 您可以处理所有错误(例如,mixin 或日志库),在这种情况下,您应该使用 try/catch 包装并处理所有错误

      【讨论】:

      • 我的课程不会抛出异常,因为它们真的不需要。如果一个函数失败,它就会失败并返回 false。令我困惑的是,如果 STL 抛出异常,这是否意味着我的班级正在抛出异常……答案似乎是肯定的,所以我的班级不是没有异常的。如果我让我的类异常免费并且不将所有 STL 包装在 try/catch 中,那么我就不会称它为免费异常。这有什么问题吗?
      • @test 如果我调用isItRainingOut() 并返回false,我希望它没有下雨,而不是isItRainingOut 使用的STL 函数抛出异常。如果您不是这种情况-例如如果您的操作实际上是 void 操作并且您返回它是否成功 - 然后包装 try/catch 并执行 catch(exc) { return false; }。这是 C 风格,替换了异常。
      • @djechlin:我同意“忽略异常可能是错误的”,但我不同意“您应该为所有可能出错的事情使用异常”。这实际上取决于您期望出错的地方以及出错的频率。抛出异常通常比返回错误状态更“昂贵”。因此,如果该函数的目的是“检查文件是否可以打开”,那么返回一个真/假值是正确的做法。如果目的是打开您希望在那里的文件,那么抛出异常是一个好计划。总的来说,这真的取决于代码的用途......
      • @MatsPetersson 如果checkIfFileCanBeOpened 无法判断文件是否可以打开——例如,如果它的 IO 块被中断(我在 Java 中考虑这个例子)——那么它应该抛出一个例外而不是返回false。如果它检查文件是否可以打开然后它不能打开,那么该函数没有错误并成功确定它不能,并且应该返回false。所以,是的,这是一个错误并且功能无法完成,或者它是否以一些“不幸”的结果完成是微妙的,但我认为这总是可以推理和决定的。
      • 例如,我更多地想像fileExists() 这样的东西。我同意,有一个决定是要抛出异常,返回某种错误代码,还是在打印一些合适的消息后调用assertabortterminate - 很大程度上取决于可以期望调用代码来解决它...
      【解决方案5】:

      在现代 C++ 中,您可以使用 noexcept

      struct Foo {
        void bar() noexcept(true) {
          some_function_that_throws();
        }
      };
      
      void safe_function() {
        // safe as nothing here can ever throw
        Foo* foo = new(std::nothrow) Foo();
        if (foo)
          foo->bar();
        delete foo;
      }
      

      这里需要注意的是,如果抛出异常并且没有在 noexcept 方法中捕获,则进程将退出。这可能被某些人认为是一件好事;它类似于使用断言。该行为最终类似于不担心异常,因为未捕获的异常通常会终止进程。主要区别在于,在适当的时候虔诚地使用 noexcept 函数可以生成更快、更精简的代码(即使在所谓的“零开销异常”ABI 中也是如此)。

      在某些行业中,简单地使用 -fno-exceptions 或等效项进行编译是常见做法。 STL 实现通常支持这种模式。有些提供了一些稍微不标准的行为,以便在需要此类检查的情况下检查或处理故障。请记住,大多数 STL 异常在某种程度上源于内存不足的情况,即使出现异常,您也几乎无法进行太多恢复;简单地崩溃与无论如何都会发生的其他事情没有太大区别。在某些应用程序中,处理这些情况很重要,但绝大多数情况下您没有处理其中一种情况(如果是,C++ 可能不是最好的语言选择)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-05-19
        • 2016-07-10
        • 2019-07-05
        • 2019-12-14
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多