【问题标题】:What are guidelines of whether to throw an exception in C++ standard function?在 C++ 标准函数中是否抛出异常的准则是什么?
【发布时间】:2011-10-08 16:34:01
【问题描述】:

我发现 C++ 标准函数在出现异常时表现出非常不同的行为。这似乎与其吹捧的“尝试/投掷/接球”相矛盾。谁能简要解释一下 C++ 设计者做出这些选择的原因是什么?

  1. 什么都不做,例如,尝试在堆栈为空时 pop()(而不是抛出 range_error),执行 sqrt(-1)(而不是抛出 domain_error)

    李>
  2. 返回一个零指针:例如在进行非法指针向下转换时(有趣的是,进行非法引用向下转换会抛出一个bad_cast)

  3. 抛出异常,但这对少数函数来说似乎是这样,例如 substr()

  4. 让用户选择是否抛出异常,例如new()在内存不足时会抛出bad_alloc(),但你也可以选择(nothrow)作为new()的选项。

【问题讨论】:

  • 您的问题与“处理异常”有什么关系?你只讲错误条件是否抛出异常!

标签: c++ exception-handling


【解决方案1】:

C++ 库函数的大部分行为都可以用 C++ 的一般哲学“不用为不用的东西付费”来解释。这意味着当您正确使用它时,任何特定的构造都不应该产生任何不必要的开销。

可选地,可能存在更昂贵的检查版本,例如std::vector::at(),但是否使用它们取决于您。

stack::pop()sqrt() 的示例显示了这一理念的实际应用:为了在出错时抛出异常,您总是必须检查调用是否有效。如果您已经知道您的调用将成功,则无需进行此检查,因此这些函数中没有内置强制检查。如果你想要一张支票,你可以自己写一张。

默认的new 略有不同,因为它包含调用new_handler 的功能,因此无论如何都会进行检查。 (回想一下,只有当您实际抛出异常时,异常才会变得昂贵,因此该方面并不那么重要。)如果您愿意,您可以随时将自己的全局 operator new() 替换为实际上只是将参数转发给 @987654327 @。 (这当然会使使用默认的new 表达式变得不安全,因为您现在无法检查是否可以在返回的指针处构造一个对象。因此您最终将自己编写检查并使用放置- new,这几乎正是 nothrow-version 所做的。)

【讨论】:

    【解决方案2】:

    返回一个零指针:例如在进行非法指针向下转换时(有趣的是,进行非法引用向下转换会抛出一个bad_cast)

    dynamic_cast 提供了一种检查强制转换有效性的方法。这绝不是一个例外。使用指针,强制转换返回NULL,因为抛出异常将是一种开销,同样可以通过返回NULL来实现,但引用不能是NULL ,除了抛出异常别无选择,在这种情况下没有其他方法可以将结果返回给用户。

    让用户选择是否抛出异常,例如new()在内存不足时会抛出bad_alloc(),但你也可以选择(nothrow)作为new()的一个选项。

    很久以前new 只是返回NULL,就像malloc 的情况一样,但后来它被标准化为抛出bad_alloc 异常,这意味着以前使用new 编写的所有代码都必须是进行了很大程度的修改以处理异常,为了避免这种情况并保持兼容性,引入了 new 的 nothrow 版本。

    【讨论】:

      【解决方案3】:

      返回 null 的指针向下转换是测试给定对象是否属于给定子类的一种更简单、更快捷的方法。 IE。你可以写if (dynamic_cast<A*>(v) || dynamic_cast<B*>(v))if (A* a = dynamtic_cast<A*>(v)) doStuffWith(a); 之类的东西,如果有例外,这会很麻烦。在这里,您实际上希望强制转换失败,而异常本质上是异常的,因为它们应该在程序的正常执行期间很少被抛出。

      在其他情况下,出于性能原因,可能会省略对不正确值的显式检查。 C++ 应该是高效的,而不是试图阻止一个人射中自己的腿。

      【讨论】:

        【解决方案4】:

        这大部分取决于是否有足够合适的返回值来表达“失败”条件,以及这些值是否方便。

        引用示例背后的原理

        std::stack<>::pop() 不会抛出错误,因为它没有错误值并且它简化了调用代码。尝试弹出空堆栈可能不是应用程序逻辑错误。

        std::sqrt() 有一个适当的值(不是数字),正是为此目的,它包含在浮点表示中。它还具有通过其他计算干净地传播的好处。

        dynamic_cast<> 在指针类型上返回一个空指针以指示强制转换失败,因为空指针已经是表示“指向无”的标准方式。但是,引用没有这样的等价物,因此它必须抛出异常,因为返回值不能用于指示错误。

        相比之下,std::string::substr() 不能返回空字符串来表示失败,因为空字符串是所有字符串的有效子字符串。

        new 是否抛出 std::bad_alloc 似乎有历史渊源,但是,就像指针上的 dynamic_cast<> 一样,对于一些尝试替代方案而不为异常处理付费的代码来说可能很方便。

        【讨论】:

        • 默认栈容器(deque)的pop_back()的前提条件是!empty(),所以如果为空则弹出总是错误的。 c++ 可能会尝试通过检查前提条件来避免增加的开销(示例 vector[] 与 vector.at())
        • 你对pop 的理性毫无意义。弹出一个空堆栈总是是一个错误,因为 [在 C++ 中] 它会导致未定义的行为。并且异常不会使调用代码复杂化...如果您不准备处理错误,则无论有无异常,调用代码都是相同的。此外,sqrt(-1) 的返回值是实现定义的;标准不保证 NaN。
        • @DennisZickefoose:确实,不能保证“不是数字”,因为该标准不假设 IEEE754 在实现上可用(或有效)。但是,所有符合 IEEE754 的实现都需要为 sqrt(-N) 返回“非数字”。至于std::stack<>::pop(),我手头没有标准的副本,但是您报告的行为没有记录在 SGI STL 站点、CppReference 或 CPlusPlus .com 上。 std::vector<>std::list<> 的成员函数 .pop_back() 也没有报告。
        • @DennisZickefoose:如果弹出空堆栈不是应用程序错误,std::stack<>::pop() 不会抛出错误确实简化代码,因为它避免了编写虚拟异常块明确地使异常静音。
        • @AndréCaron:pop_back [和其他此类功能] 的前提条件是容器不能为空。每当您违反标准提出的前提条件时,结果都是未定义的。我不知道您如何定义“应用程序错误”,但是使您的程序处于不确定状态的操作对我来说肯定听起来很糟糕。如果你觉得有必要在每个可能抛出的函数周围放置一个虚拟异常块,那么你使用的异常是错误的。这不是 Java,可以让异常传播。
        【解决方案5】:

        c++ 标准库被设计为高效,它尽可能避免不必要的运行时检查。

        1 在非常小/快速的方法上包含对先决条件的违反,检查这些先决条件很可能比执行方法本身花费更长的时间(pop 很可能是简单类型堆栈上的单个减量)。

        2 dynamic_cast 检查并将给定指针转换为兼容类型。没有单独的方法来检查是否可以进行演员表,因为它必须做与演员表相同的工作。由于 c++ 没有提供单独的方法来仅检查强制转换是否可能,我们必须预期它可能会失败,并且它有一个很好的错误值可以在失败时使用(NULL)。参考版本必须抛出异常,因为它不能返回错误值。

        3 substr 保证异常,这可能有两个原因。一:substr 方法比 1 中提到的方法更复杂和慢,因此检查前提条件的开销可以忽略不计。二:字符串处理是安全漏洞的最大贡献者之一,因为您很可能处理用户输入,检查溢出或越界访问对于保持过程安全/稳定是必要的。 c-library 为那些需要速度的人提供了快速、未经检查和不安全的字符串操作方法。

        4 new 必须检查它是否可以返回地址或在这两种情况下都失败,因为大多数应用程序都无法预料到内存不足,该异常是合理的。但是,您可以在使用其功能的一小部分时编写 c++,并且许多项目不使用异常,因为使您的代码异常安全很难(特别是如果您使用不安全的第三方库),因为 new 是 c++ 的核心部分无异常的实现变得必要。

        【讨论】:

          猜你喜欢
          • 2011-02-19
          • 2016-11-24
          • 2016-03-09
          • 2010-10-24
          • 2019-01-20
          • 2017-02-19
          • 2012-10-04
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多