129

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

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

4

14 回答 14

101

不。

以下是几个例子:

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

    g将终止,当lib_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;
    }
    

然而,当您的库只是抛出您自己的异常时,您可以使用异常规范来说明您的意图。

于 2008-09-18T00:26:36.477 回答
42

避免 C++ 中的异常规范。您在问题中给出的原因是一个很好的开始。

请参阅 Herb Sutter 的“A Pragmatic Look at Exception Specifications”

于 2008-09-17T23:21:40.283 回答
14

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

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

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

例外保证

无保证:

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

基本保证:

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

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

这保证了方法将成功完成,
否则将抛出异常并且对象状态不会改变。

无投掷保证:

该方法保证不允许任何异常传播到该方法之外。
所有的析构函数都应该做出这个保证。
| NB 如果在异常已经传播时异常从析构函数中转义
| 应用程序将终止

于 2008-09-18T03:38:37.047 回答
8

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

于 2008-09-18T03:26:36.903 回答
7

唯一有用的异常说明符是“throw()”,如“不抛出”。

于 2008-09-18T20:29:26.790 回答
4

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

我在一些项目中所做的是带有异常规范的代码,然后调用 set_unexpected() 函数,该函数将抛出我自己设计的特殊异常。此异常在构造时获得回溯(以特定于平台的方式)并从 std::bad_exception 派生(以允许在需要时传播它)。如果它像通常那样导致终止()调用,则回溯由what()打印(以及导致它的原始异常;不难找到),所以我得到了我的合同在哪里的信息违反,例如抛出什么意外的库异常。

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

于 2008-10-20T19:17:40.467 回答
3

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

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

于 2008-09-17T23:13:54.500 回答
3

否。如果您使用它们并且抛出了您未指定的异常,无论是由您的代码还是由您的代码调用的代码,那么默认行为是立即终止您的程序。

此外,我相信它们的使用在当前的 C++0x 标准草案中已被弃用。

于 2008-09-19T01:49:04.413 回答
3

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

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

于 2010-03-01T15:13:31.590 回答
2

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

是的,从具有异常说明符的函数抛出的未指定异常的预期行为是调用 terminate()。

我还将注意到 Scott Meyers 在更有效的 C++ 中解决了这个主题。他的 Effective C++ 和 More Effective C++ 是强烈推荐的书籍。

于 2008-09-17T23:12:59.617 回答
2

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

我同意,它们对于在编译器中强制 Java 风格的正确性并没有真正有用,但总比没有或随意的评论好。

于 2008-09-17T23:17:13.243 回答
2

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

于 2008-09-17T23:26:58.773 回答
0

来自文章:

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

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

事实上,我可以想办法让模板类异常安全。除非您无法控制所有子类,否则无论如何您都可能遇到问题。为此,可以在您的类中创建定义各种模板类抛出的异常的 typedef。这认为问题总是在事后解决,而不是从一开始就设计它,我认为真正的障碍是这种开销。

于 2009-12-21T17:11:39.740 回答
-2

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

于 2008-10-20T19:22:28.850 回答