功能异常规范和标准异常 - foo() throw(Exception)

4
在C++中,您可以像这样声明带有异常规范的函数:
int foo() const throw(Exception);

我找到了这两个链接: 但是有几个问题没有解决... 问题1:为什么要添加异常说明?它会带来任何性能提升吗?编译器会有什么不同的表现?因为对我来说,它似乎只是提供给程序员的信息。 问题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_allocbad_castbad_exceptionbad_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,...)

注意:带有参考文献的答案会很棒。我正在寻找好的实践技巧。

注:本文提供的参考资料将会很有用。我正在寻找一些好的实践技巧。

1
不要使用异常规范,它们在C++11中已经被废弃且无用。唯一有用的部分是throw(),但在C++11中已被noexcept取代。 - Cat Plus Plus
4个回答

6

一个简单的“好习惯”提示是:不要使用异常规范。

唯一的例外情况是可能会出现空异常规范:throw()。这非常有用,以至于在C++11中,它被赋予了自己的关键字(noexcept)。一般认为,任何非空异常规范都是一个糟糕的想法。

除了noexcept,异常规范已被正式弃用——与许多弃用功能不同的是,删除它将影响很少的源代码,我认为它最终真的会被删除(当然不能保证,但是相当有可能)。

如果您抛出一个不允许的异常类型,则会调用std::unexpected()。默认情况下,它会调用terminate()。您可以使用std::set_unexpected来设置自己的处理程序——但是您可以合理地做的就只有在terminate()之前添加一些日志记录。您的意外处理程序不允许返回。


我不确定关于“从未被广泛使用”。std::exception中的函数有它们,这意味着任何派生自己的异常类的人都必须使用它们。(诚然是throw()。) - James Kanze
@JamesKanze:谢谢 - 我已经重新表述了,使其更准确。 - Jerry Coffin
你能提供关于"It's generally agreed that any non-empty exception specification is a lousy idea though."的参考资料吗?我的项目负责人不认为"在stackoverflow上回答"是可靠的来源(尽管你的声誉相当引人注目):-/ - Vyktor
@Vyktor:因为它写起来很麻烦,而且并不能真正保护你免受任何问题的影响。 - Cat Plus Plus
一个可能的选择是Guru of the Week #82(Herb Sutter)。 - Jerry Coffin
显示剩余5条评论

3

问题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
  • 当某些操作失败时,输入/输出库(<iostream>等)会抛出ios_base::failure

问题5

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


2
首先,让我们非常清楚地了解一下异常规范的作用:它更像是一个无法禁用的assert,断言您不会因为除缺失之外的其他异常而退出函数。因此,它的效用比起初看起来要有限得多;在大多数情况下(在这种情况下,我无法想象一个例外),唯一真正有用的保证是throw(),它保证不会抛出任何异常;如果您想编写异常安全代码,您需要这个保证来处理一些底层函数。
实际上,尽管throw()可以允许一些额外的编译器优化,在使用异常规范时,通用实现往往会导致不太高效的代码。在C++11中,throw()已被noexcept取代,可能是希望编译器实现者能对其进行智能处理。
编辑:
由于每个人(包括我自己)似乎都忽略了你的第4个问题:
如果operator new无法分配内存,则会抛出bad_alloc
如果转换失败,则会通过对引用的dynamic_cast抛出bad_cast。(对指针的dynamic_cast在这种情况下返回空指针。)
如果违反了异常规范,并且异常规范允许bad_exception,则会抛出bad_exception。(换句话说,请忘记它。)
如果您尝试使用typeid来处理空指针,则会抛出bad_typeid
如果您请求流在出现错误时抛出异常,则会抛出ios_base::failure
实际上:如果您想从内存不足的情况中恢复并继续执行,则会捕获bad_alloc。这意味着不太经常。(从内存不足的情况中恢复非常困难。)对于bad_cast,如果您不确定,最好使用指针并测试null。永远没有借口看到bad_typeid。大多数情况下,您可能希望显式测试IO错误,而不是配置流以引发异常;当设置ios_base::badbit时,引发异常可能是一个例外(因为它表示硬件故障的真正特殊情况)。

1

问题1和2在这个问题中有详细解答。

问题3和5可以通过该问题的被接受答案中的建议得到解决,即不使用任何异常规范。

问题4似乎已经得到了充分的解答,您可以将这些异常名称输入您选择的搜索引擎中,或者查阅一本好的C++书籍的索引。您有关于它们的具体查询吗?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接