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


123

在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(...),因此对标准的遵循性不强。

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


7
“ throw(...)”不是标​​准的C ++。我相信它是某些编译器的扩展,通常与没有异常规范的含义相同。
理查德·科登,

Answers:


97

没有。

这是为什么的几个示例:

  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();
    }

    glib_f()抛出时将终止。(在大多数情况下)这不是您真正想要的。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;
    }

不过,当您的库仅引发您自己的异常时,您可以使用异常规范说明您的意图。


1
在3中,从技术上讲它将是std ::意外的,而不是std :: terminate。但是,当调用此函数时,默认值将调用abort()。这将生成一个核心转储。这比未处理的异常差多少?(基本上也做同样的事情)
格雷格·罗杰斯

6
@Greg Rogers:未捕获的异常仍然会消除堆栈。这意味着将调用析构函数。在那些析构函数中,可以做很多事情,例如:正确释放资源,正确写入日志,将告知其他进程当前进程崩溃等。总而言之,它是RAII。
paercebal

您没有进行任何操作:这有效地将所有内容包装在try {...} catch (<specified exceptions>) { <do whatever> } catch (...) { unexpected(); ]构造中,无论您是否要在其中尝试try块。
David Thornley 2010年

4
@paercebal那是不正确的。由实现定义,是否针对未捕获的异常运行析构函数。如果未捕获到异常,大多数环境不会释放堆栈/运行析构函数。如果您想甚至在引发异常且未处理异常时(即使是可疑的值)可移植地确保析构函数也能运行,则需要编写代码,例如try { <<...code...>> } catch(...) /* stack guaranteed to be unwound here and dtors run */ { throw; /* pass it on to the runtime */ }
Logan Capaldo 2010年

让应用程序以未处理的异常崩溃(从中可以检索堆栈跟踪)总是比静默/暴力地消亡总是更好的。 ”“崩溃”怎么可能比干净的调用更好terminate()?为什么不只是打电话abort()
curiousguy

42

避免使用C ++中的异常规范。您提出问题的理由是一个很好的起点。

参见Herb Sutter的“对异常规范的实用观察”


3
@awoodland:throw(optional-type-id-list)在C ++ 11中不赞成使用'dynamic-exception-specifications'()。它们仍处于标准状态,但是我想已经发出警告,请谨慎考虑使用它们。C ++ 11添加了noexcept规范和运算符。我尚无足够noexcept评论要评论的细节。该文章似乎非常详细:akrzemi1.wordpress.com/2011/06/10/using-noexcept And DietmarKühl在2011年6月的《超载杂志》上有一篇文章:accu.org/var/uploads/journals/overload103.pdf
迈克尔·伯(Michael Burr)2012年

@MichaelBurr Only throw(something)被认为是无用的,不是一个好主意。throw()是有用的。
curiousguy

以下是许多人认为异常规范所执行的操作:bullet确保函数仅会抛出列出的异常(可能没有)。bullet基于仅会抛出列出的异常(可能没有)的知识来启用编译器优化。以上期望是,再次,看似接近正确 “”不,以上期望绝对正确。
curiousguy

14

我认为除约定外(对于C ++),标准
例外说明符是在C ++标准中进行的一项实验,多数失败。
唯一的例外是“不抛出”说明符很有用,但您还应该在内部添加适当的try catch块以确保代码与说明符匹配。Herb Sutter在此主题上有一页。戈特82

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

这些基本上是关于对象的状态如何受到异常转义的方法的文档。不幸的是,它们没有被编译器强制执行或以其他方式提及。
提升和例外

例外保证

不保证:

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

基本保证:

在几乎所有情况下,这都应该是方法提供的最低保证。
这保证了对象的状态定义正确,并且仍然可以被一致地使用。

强有力的保证:(又名交易保证)

这保证了该方法将成功完成,
否则将引发异常并且对象状态不会更改。

无掷保证:

该方法保证不允许异常传播到该方法之外。
所有破坏者都应作出此保证。
| 注意:如果在已传播异常的情况下异常逃脱了析构函数
| 该应用程序将终止


每个C ++程序员都必须知道这些保证,但是在我看来,这些保证与异常规范无关。
David Thornley 2010年

1
@David Thornley:我将保证视为异常规范应该具有的保证(即,具有强G的方法不能调用没有保护的具有基本G的方法)。不幸的是,我不确定它们的定义是否足够好,是否可以以有用的方式强制执行编译器。
马丁·约克2010年

以下是许多人认为异常规范所做的事情:-确保函数仅抛出列出的异常(可能没有)。-基于仅抛出列出的异常(可能没有)的知识启用编译器优化。以上期望是,再次,看似接近是正确。 “其实,无论是正好合适的。
curiousguy

@curiousguy。这就是Java的方法,因为它在编译时强制执行检查。C ++是运行时检查。因此:Guarantee that functions will only throw listed exceptions (possibly none)。不对。它仅保证如果函数确实抛出这些异常,则应用程序将终止。Enable compiler optimizations based on the knowledge that only listed exceptions (possibly none) will be thrown做不到 因为我只是指出您不能保证不会引发异常。
马丁·约克

1
@LokiAstari“这是Java的方法,因为它在编译时强制执行检查_” Java异常规范是一个失败的实验。Java没有最有用的异常规范:(throw()不抛出)。“ 这仅保证如果函数确实引发了这些异常,则应用程序将终止。 “否“不正确”表示正确。在C ++中,无法保证函数terminate()永远不会调用。“ 因为我只是指出您不能保证不会引发异常 ”您保证函数不会引发。正是您所需要的。
curiousguy

8

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



4

异常规范不是C ++中非常有用的工具。但是,如果将它们与std :: unexpected结合使用,则/ is /有很好的用途。

在某些项目中,我要做的是编写具有异常规范的代码,然后使用将抛出我自己设计的特殊异常的函数调用set_unexpected()。构造后,此异常获取回溯(以特定于平台的方式),并从std :: bad_exception派生(如果需要,可以传播它)。如果它像通常那样引起了一个terate()调用,则回溯将由what()打印(以及引起它的原始异常;不难发现),因此我获得了我的合同在哪里的信息。违反,例如引发了意外的库异常。

如果这样做,我将永远不允许传播库异常(std异常除外),并从std :: exception派生我的所有异常。如果库决定抛出,则我将捕获并转换为自己的层次结构,从而使我能够始终控制代码。由于明显的原因,调用依赖函数的模板化函数应避免使用异常规范。但是无论如何,很少有带有库代码的模板化函数接口(很少有库确实以有用的方式使用模板)。


3

如果您要编写的代码供人们使用,而他们宁愿看一下函数声明而不是周围的任何注释,那么规范将告诉他们他们可能想捕获哪些异常。

否则,我觉得使用任何东西并不是特别有用,只是throw()表明它不会引发任何异常。


3

否。如果使用它们,并且引发了未指定的异常(无论是通过代码还是由代码调用的代码),则默认行为是立即终止程序。

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



2

通常,我不会使用异常说明符。但是,如果所讨论的功能会导致其他任何异常导致程序肯定无法更正,则此功能很有用。在所有情况下,请确保清楚地记录该功能可能发生的异常情况。

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

我还将注意到,斯科特·迈耶斯(Scott Meyers)在《更有效的C ++》中解决了这个问题。强烈推荐他的有效C ++和更有效C ++。


2

是的,如果您需要内部文档。或者也许写一个别人可以使用的库,这样他们就可以知道发生了什么而无需查阅文档。抛出或不抛出都可以视为API的一部分,就像返回值一样。

我同意,它们对于在编译器中强制正确性Java样式并不是真正有用,但是总比没有注释或随意注释好。


2

它们对于单元测试很有用,因此在编写测试时,您会知道该函数在失败时会抛出什么结果,但是在编译器中不会对其进行强制执行。我认为它们是C ++中不必要的额外代码。您应该确保选择哪种方法,就是在项目和团队成员之间遵循相同的编码标准,从而使代码保持可读性。


0

从文章:

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

“众所周知,编写异常安全的通用容器是不可能的。” 通常会参考Tom Cargill [4]的一篇文章听到这种说法,他在其中探讨了通用堆栈模板的异常安全性问题。嘉吉在他的文章中提出了许多有用的问题,但不幸的是未能提出解决问题的方法。1他最后指出,不可能解决。不幸的是,许多人认为他的文章是这种推测的“证明”。自从它发布以来,已经有很多异常安全通用组件的示例,其中包括C ++标准库容器。

实际上,我可以想到使模板类异常安全的方法。除非您无法控制所有子类,否则您可能仍然会遇到问题。为此,您可以在您的类中创建typedef,以定义各种模板类引发的异常。这种想法是一如既往地将其解决,而不是一开始就进行设计。我认为,这是真正的障碍。


-2

异常规范=垃圾,请问30岁以上的任何Java开发人员


8
30岁以上的Java程序员应该感到很难受,他们无法应付C,他们没有借口使用Java。
马特·乔纳
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.