【问题标题】:Function exceptions specification and standard exceptions - foo() throw(Exception)函数异常规范和标准异常 - foo() throw(Exception)
【发布时间】:2012-03-14 03:52:06
【问题描述】:

在 C++ 中,您可以像这样声明带有异常规范的函数:

int foo() const throw(Exception);

我找到了这两个链接:

但有几件事最终没有得到答复......

问题一:为什么要添加异常规范?它会带来任何性能提升吗?编译器会有什么不同?因为它对我来说似乎只是程序员的信息。

问题 2:如果我抛出不符合规范的东西会发生什么(应该发生什么)?例如:

int foo() throw(int) {
        throw char; // Totally unrelated classes, not types in real
}

问题 3: 函数/方法不应该抛出任何东西。我发现了至少两种(三种,不同编译器的替代语法)方法来指定不抛出异常:

  • int foo() throw();
  • int foo() __attribute(nothrow)__ 用于 gcc
  • int foo() nothrow 用于 Visual C++

哪一个是“正确的”?有什么区别吗?我应该使用哪一个?

问题 4:“标准异常”,bad_alloc,bad_cast,bad_exception,bad_typeidios_base::failure

好的bad_alloc 是自我解释的,我知道如何(更重要的是何时)使用它(添加到异常规范中),但是其他的呢?他们都没有真正敲响警钟......他们与哪些“代码片段”相关联?就像bad_allocnew char[500000] 相关联。

问题 5:如果我有异常类层次结构,像这样:

    class ExceptionFileType {
             virtual const char * getError() const = 0;
    };

    class ExceptionFileTypeMissing : public ExceptionFileType {
            virtual const char *getError() cosnt {
                    return "Missing file";
            }
    }

我应该使用:

    int foo() throw(ExceptionFileType);

或者:

    int foo() throw(ExceptionFileTypeMissing,ExceptionFileTypeNotWritable,ExceptionFileTypeNotReadable,...)

注意:有参考的答案会很棒。我正在寻找好的练习技巧。

【问题讨论】:

标签: c++ exception exception-handling throw


【解决方案1】:

简单的“良好做法”提示是:不要使用异常规范。

基本上唯一的例外是空异常规范的可能性:throw()。这非常有用,在 C++11 中它被赋予了自己的关键字 (noexcept)。人们普遍认为,任何非空异常规范都是一个糟糕的想法。

异常规范(noexcept 除外)已正式弃用 - 与许多弃用的功能不同,删除它只会影响足够少的源代码,我认为它很有可能最终会被删除(当然不能保证,但无论如何,这是一个相当公平的机会)。

至于当/如果您确实抛出异常规范不允许的类型的异常时会发生什么:std::unexpected() 被调用。默认情况下,它调用terminate()。您可以使用std::set_unexpected 设置您自己的处理程序——但您可以合理地做的就是在terminate() 之前添加一些日志记录。不允许您的意外处理程序返回。

【讨论】:

  • 我不确定“从未使用过多少”。 std::exception 中的函数有它们,这意味着任何派生自己的异常类的人都必须使用它们。 (诚​​然throw()。)
  • @JamesKanze:谢谢——我已经改写了它以更准确。
  • 您能否为“尽管普遍认为任何非空异常规范都是一个糟糕的想法”提供一些参考?我的项目负责人不会接受“stackoverflow 上的答案”作为可靠来源(尽管您的声誉令人印象深刻):-/
  • @Vyktor:因为写起来很麻烦,而且不能真正保护你免受任何伤害。
  • 一种可能性是Guru of the Week #82 (Herb Sutter)。
【解决方案2】:

问题 1

不要打扰。它们是一个坏主意,并且在该语言的最新版本中已被弃用。它们对编译器没有任何好处,因为它们是在运行时检查的;如果有的话,它们在某些情况下可能会损害性能。

问题 2

将调用一个名为std::unexpected 的函数。默认情况下,这会调用std::terminate;默认情况下,这会终止程序。如果你真的想的话,这两种行为都可以改变,使用std::set_unexpectedstd::set_terminate 来安装你自己的处理程序。

问题 3

throw() 是标准的做法;其他的是不可移植的编译器扩展。在 C++11 中,您可能会使用 noexcept,它会在编译时检查不会抛出任何东西,而不是在运行时检查不会抛出任何东西。

问题 4

  • 当引用的dynamic_cast 失败时抛出bad_cast
  • bad_exception 在违反异常规范时在一些奇怪的情况下被抛出。
  • 如果评估 typeid 的参数涉及取消引用空指针,则会抛出 bad_typeid
  • ios_base::failure 在某些操作失败时由输入/输出库(<iostream> 等)抛出

问题 5

如果您想允许抛出整个层次结构,那么只需指定基类。但是您根本不应该使用异常说明符。

【讨论】:

    【解决方案3】:

    首先,让我们非常清楚异常规范的作用:它是 或多或少像一个不能被禁用的assert,断言你 不会退出函数,但会出现 other 异常 失踪。因此,它的效用远比看起来的要有限 首先;在大多数情况下(在这种情况下,我无法想象 例外),唯一真正有用的保证是throw(),它 保证不会抛出异常;如果你想写 异常安全代码,你需要一些低级别的保证 功能。

    实际上,虽然throw() 可以允许一些额外的编译器 优化,通用实现往往会导致更少 使用异常规范时的高效代码。在 C++11 中, throw() 已经被noexcept 取代,大概是希望 编译器实现者会用它做一些智能的事情。

    编辑:

    由于每个人(包括我自己)似乎都错过了您的问题 4:

    bad_alloc 将被 operator new 函数抛出,如果它不能 分配内存。

    bad_cast 将被 dynamic_cast 抛出到一个引用,在 演员失败的情况。 (指向指针的dynamic_cast 返回一个 这种情况下的空指针。)

    bad_exception 将在异常规范被抛出时被抛出 违反,前提是异常规范允许bad_exception。 (换句话说,算了吧。)

    如果您尝试将typeid 与null 一起使用,将抛出bad_typeid 指针。

    ios_base::failure 如果你请求一个流来抛出,将被抛出 错误的情况。

    实际上:如果你想恢复,你会抓住bad_alloc 并从内存不足的情况继续。这意味着不经常。 (从内存不足的情况中恢复非常非常困难。) 对于bad_cast,最好使用指针,并测试 null,如果您不确定。而且没有任何借口可以看到 bad_typeid。大多数时候,您可能想要测试 IO 错误 明确地,而不是将流配置为抛出异常;和 设置ios_base::badbit 时的异常可能是异常(因为它 代表一个真正例外的硬故障)。

    【讨论】:

      【解决方案4】:

      问题 1 和 2 在this question 中有详细的说明。

      问题 3 和 5 包含在建议中,在该问题的已接受答案中,您根本不使用异常规范。

      问题 4 似乎可以通过在您选择的搜索引擎中输入这些异常名称或查阅一本好 C++ 书籍的索引来充分解决。您对它们有具体的疑问吗?

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-09-25
        • 1970-01-01
        • 2013-09-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-16
        相关资源
        最近更新 更多