【问题标题】:Deprecated throw-list in C++11C++11 中弃用的 throw-list
【发布时间】:2012-12-12 14:07:00
【问题描述】:

正如我在cppreference 中看到的那样,经典的“抛出”声明列表现在在 C++11 中已弃用。离开这个机制的原因是什么?我应该如何指定哪些异常会引发我的函数?

【问题讨论】:

    标签: c++ c++11 throw


    【解决方案1】:

    更详细的推理见:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3051.html

    如上述国家机构评论中所述,异常规范在实践中并未证明有用。有很多关于 C++ 中异常规范问题的讨论(例如,参见 [Sutter02]、[Boost03]),但主要问题是:

    • 运行时检查:C++ 异常规范在运行时而不是在编译时进行检查,因此它们不提供程序员保证所有异常都已得到处理的保证。运行时故障模式(调用 std::unexpected())不适合恢复。
    • 运行时开销:运行时检查要求编译器生成额外的代码,这也会妨碍优化。
    • 在泛型代码中不可用:在泛型代码中,通常无法知道对模板参数的操作可能会引发哪些类型的异常,因此无法编写精确的异常规范。

    在实践中,只有两种形式的异常抛出保证是有用的:一个操作可能会抛出一个异常(任何异常),或者一个操作永远不会抛出任何异常。前者通过完全省略异常规范来表达,而后者可以表达为 throw() 但出于性能考虑,很少使用。

    [N3050] 引入了一种新的异常规范,noexcept,指定函数不会抛出任何异常。与 throw() 不同的是,noexcept 不需要编译器引入代码来检查是否抛出异常。相反,如果指定为 noexcept 的函数通过异常退出,结果是调用 std::terminate()。

    通过引入 noexcept,程序员现在可以表达两种在实践中有用的异常保证,而无需额外的开销。因此,本文建议弃用“动态”异常规范,即那些写为 throw(type-id-listopt) 的规范。

    【讨论】:

    • 看[except.spec],注9,在n3485中似乎动态检查部分后来被重新引入:每当抛出异常并搜索处理程序(15.3)遇到具有不允许异常的异常规范的函数的最外层块,则如果异常规范是动态异常规范,则调用函数std::unexpected() (15.5.2),否则,函数std::terminate() 被调用(15.5.1)。(强调我的);给定exception-specification的语法,otherwise子句可能只关注noexcept
    • 一个可悲的旁注是noexcept 在实践中毫无用处,因为终止程序而不是尝试处理未能声明被认为不可能的错误有什么帮助但还是发生了。 “如果我们(或其他人)失败了,就终止”似乎是一种糟糕的错误处理策略。
    • @ChristopherOezbek 这是一个完全有效的策略,因为您通常不希望您的应用程序在您从未想过可能的状态下继续执行某些操作,因此也没有编码。应用程序最好立即退出,这样您就可以立即发现问题所在。
    • @ChristopherOezbek 你肯定希望你的数据库服务器崩溃(快速失败)而不是破坏你的数据库,就像一个例子。
    • 不要死,但你必须喜欢人们为整合语言功能而付出的痛苦。真正的结论是显而易见的:不要使用异常。如果您阅读的材料比 K&R 的书更多,只是为了避免错误地使用它,那么它根本不值得使用。我们只需要多个返回值。
    【解决方案2】:

    Peter 给出的答案并没有触及实现者和用户的异常规范的实际问题:

    • 如果实现者未能保证只抛出已定义的异常,异常规范会导致程序终止(或更准确地说是调用终止处理程序)。
    • 因此,作为库用户,通过调用具有异常规范的方法,您会使自己的代码更加容易发生完全失败/终止。如果库函数内存不足(std::bad_alloc),您将没有机会捕获,但您将被终止。
    • 因此,未能实现传达最可能失败选项并要求您作为用户处理它们的最初目标。
    • 作为另一方的实现者,您不能真正调用任何其他没有异常规范的方法,因为这些可能会导致您终止调用者。一个可怕的地方。

    结论是 C++ 应该走 Java 的路:

    • 如果您调用具有异常规范的方法并且您自己有异常规范,那么您必须捕获该异常或在您自己的异常规范中指定它。
    • 编译器会强制执行此效果,但不会执行其他运行时效果。

    Noexcept (C++11 起) 存在同样的概念错误,因为如果不遵守规范也会导致运行时终止,即声明不遵守的方法抛出。这使得noexcept 无法用于任何严重的情况,但包含最多的情况 (move constructors come to mind)。

    【讨论】:

    • 满足noexcept要求其实很简单。就像自从 C++ 开始使用不能抛出的析构函数以来我们必须做的那样,将它全部包装在 try / catch(...) 块中并在返回之前对其进行处理。
    • 事实上,这是 Java 明显优于 C++ 的少数几个方面之一。
    【解决方案3】:

    它们会产生更慢和更大的代码,因为 libc++ 必须检查从函数传播的任何异常是否违反了它的异常规范并调用 std::unexpected。这几乎没有用,而且比仅仅记录一个函数自己抛出的异常更糟糕。

    【讨论】:

      猜你喜欢
      • 2012-10-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-22
      相关资源
      最近更新 更多