我应该在 C + + 中使用异常说明符吗?

在 C + + 中,可以通过使用异常说明符来指定函数是否引发异常。例如:

void foo() throw(); // guaranteed not to throw an exception
void bar() throw(int); // may throw an exception of type int
void baz() throw(...); // may throw an exception of some unspecified type

我对实际使用它们持怀疑态度,原因如下:

  1. 编译器实际上并不以任何严格的方式强制执行异常说明符,因此这样做的好处并不大。理想情况下,您希望得到一个编译错误。
  2. 如果函数违反了异常说明符,我认为标准行为是终止程序。
  3. 在 VS.Net 中,它将 throw (X)视为 throw (...) ,因此对标准的坚持并不强烈。

您认为应该使用异常说明符吗?
请回答“是”或“否”,并提供一些理由来证明你的答案。

28630 次浏览

通常我不会使用异常说明符。但是,如果有任何其他异常来自有问题的函数,那么程序肯定无法执行 正确,那么它可能是有用的。在所有情况下,确保清楚地记录该函数可能出现的异常。

是的,从具有异常说明符的函数引发的非指定异常的预期行为是调用 finally ()。

我还会注意到,Scott Meyers 在《更有效的 C + + 》一书中谈到了这个问题。他的有效的 C + + 和更有效的 C + + 是强烈推荐的书籍。

如果您正在编写的代码将被那些更愿意查看函数声明而不是其周围的任何注释的人所使用,那么一个规范将告诉他们可能希望捕获哪些异常。

否则,除了使用 throw()来表明它不抛出任何异常之外,我不觉得使用其他任何东西特别有用。

是的,如果你喜欢内部文件的话。或者编写一个其他人会使用的库,这样他们就可以在不查阅文档的情况下知道发生了什么。抛出或不抛出可以被认为是 API 的一部分,几乎与返回值一样。

我同意,它们对于在编译器中强制执行正确的 Java 风格并不真正有用,但总比什么都没有或随意的注释要好。

避免使用 C + + 中的异常规范。你在问题中给出的理由是一个很好的开始。

参见 Herb Sutter 的 “从实用角度看异常规范”

它们对于单元测试非常有用,这样在编写测试时,您就知道当函数失败时,函数将抛出什么,但是在编译器中没有围绕它们的强制。我认为它们是 C + + 中不需要的额外代码。无论您选择哪种方式,您都应该确保在整个项目和团队成员中遵循相同的编码标准,以便您的代码保持可读性。

没有。

以下是几个例子:

  1. 模板代码不可能用异常规范来编写,

    template<class T>
    void f( T k )
    {
    T x( k );
    x.x();
    }
    

    副本可能抛出,参数传递可能抛出,而 x()可能抛出一些未知的异常。

  2. 异常-规范倾向于禁止扩展性。

    virtual void open() throw( FileNotFound );
    

    可能会演变成

    virtual void open() throw( FileNotFound, SocketNotReady, InterprocessObjectNotImplemented, HardwareUnresponsive );
    

    你可以把它写成

    throw( ... )
    

    第一个是不可扩展的,第二个是过于雄心勃勃的,第三个就是您在编写虚函数时的真正含义。

  3. 遗产密码

    当您编写依赖于另一个库的代码时,您并不真正知道当出现严重错误时它会做什么。

    int lib_f();
    
    
    void g() throw( k_too_small_exception )
    {
    int k = lib_f();
    if( k < 0 ) throw k_too_small_exception();
    }
    

    lib_f()抛出时,g将终止。这(在大多数情况下)不是你真正想要的。不应该调用 std::terminate()。让应用程序因为未处理的异常(可以从中检索堆栈跟踪)而崩溃,总比静默/剧烈地死亡要好。

  4. 编写返回常见错误并在异常情况下抛出的代码。

    Error e = open( "bla.txt" );
    if( e == FileNotFound )
    MessageUser( "File bla.txt not found" );
    if( e == AccessDenied )
    MessageUser( "Failed to open bla.txt, because we don't have read rights ..." );
    if( e != Success )
    MessageUser( "Failed due to some other error, error code = " + itoa( e ) );
    
    
    try
    {
    std::vector<TObj> k( 1000 );
    // ...
    }
    catch( const bad_alloc& b )
    {
    MessageUser( "out of memory, exiting process" );
    throw;
    }
    

Nevertheless, when your library just throws your own exceptions, you can use exception specifications to state your intent.

当您违反异常规范时,gcc 将发出警告。我所做的是使用宏来使用异常规范,只在“ lint”模式下编译,以明确检查,以确保异常与我的文档一致。

我认为标准的除了约定(对于 C + +)
异常说明符是 C + + 标准中的一个实验,但大多数都失败了。
唯一的例外是,no throw 说明符是有用的,但是您还应该在内部添加适当的 try catch 块,以确保代码与说明符匹配。Herb Sutter 有一页关于这个主题的文章。哥特82

另外,我认为值得描述异常保证。

这些基本上是关于对象的状态如何受到逃逸该对象上方法的异常的影响的文档。不幸的是,编译器没有强制或以其他方式提到它们。
提升与例外

例外保证

无担保:

在异常转义方法之后,不能保证对象的状态
在这些情况下,不应该再使用该对象。

基本保证:

在几乎所有情况下,这应该是方法提供的最小保证。
这保证了对象的状态定义良好,并且仍然可以始终如一地使用。

有力担保: (又称交易担保)

这保证了该方法将成功地完成
否则将引发一个 Exception 并且对象状态不会更改。

无投掷保证:

该方法保证不允许异常从该方法传播出去。
所有的析构函数都应该做出这样的保证。
注意: 当异常已经在传播时,如果异常逃脱析构函数
应用程序将终止

唯一有用的异常说明符是“ throw ()”,如“ doesn’t throw”。

没有。如果您使用了它们,并且抛出了您没有指定的异常(由您的代码或由您的代码调用的代码) ,那么默认行为是立即终止您的程序。

另外,我相信在 C + + 0x 标准的当前草案中已经不再使用它们了。

异常规范在 C + + 中并不是非常有用的工具。但是,如果与 std: : 意外地结合使用,则可以很好地使用它们。

我在一些项目中使用的是带有异常规范的代码,然后使用一个函数调用 set _  () ,该函数将抛出我自己设计的特殊异常。这个异常在构造时获得一个回溯跟踪(以特定于平台的方式) ,并从 std: : bad _ eption 派生(以允许在需要时传播它)。如果它像通常那样导致了 finally ()调用,那么回溯跟踪将由 what ()(以及导致它的原始异常打印出来; 不难发现) ,因此我将获得违反契约的地方的信息,比如抛出了什么意外的库异常。

如果这样做,我就不会允许库异常的传播(除了 std 异常) ,并从 std: : 异常派生所有异常。如果库决定抛出,我将捕获并转换成我自己的层次结构,允许我始终控制代码。调用依赖函数的模板化函数应该避免异常规范,原因显而易见; 但是很少有函数接口带有库代码的模板化函数(而且很少有库真正以有用的方式使用模板)。

异常规范 = 垃圾,问问任何超过30岁的 Java 开发人员

摘自文章:

Http://www.boost.org/community/exception_safety.html

众所周知 编写一个异常安全的泛型 这种说法经常被听到 参考汤姆的一篇文章 嘉吉公司[4] ,他在其中探索了 异常安全问题 通用堆栈模板。在他的 文章中,嘉吉提出了许多有用的 问题,但不幸的是没有 提出解决他问题的办法 最后建议 解决方案可能是不可能的。 不幸的是,他的文章被 许多都是这种猜测的“证据”。 自从这本书出版以来 例外安全的许多例子 通用组件,其中包括 C + + 标准图书馆容器。

实际上,我可以想出使模板类异常安全的方法。除非你不能控制所有的子类,否则你可能会有一个问题。要做到这一点,可以在类中创建 typedef,定义由各种模板类引发的异常。这种想法认为问题总是在事后加以解决,而不是从一开始就设计出来,我认为这种开销才是真正的障碍。

“ throw ()”规范允许编译器在进行代码流分析时执行一些优化,如果它知道函数永远不会抛出异常(或者至少承诺永远不会抛出异常)。拉里•奥斯特曼(Larry Osterman)在这里简要谈到了这一点:

Http://blogs.msdn.com/larryosterman/archive/2006/03/22/558390.aspx