it-swarm.cn

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

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

119
1800 INFORMATION

没有。

以下是几个例子:

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

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

91
Christopher

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

参见Herb Sutter's “实用的异常规范”

42
Michael Burr

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

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

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

例外保证

无保证:

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

基本保证:

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

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

这可以保证该方法成功完成
或者抛出异常并且对象状态不会改变。

无投保证:

该方法保证不允许任何异常传播出该方法。
所有的破坏者都应该做出这种保证。
|注:如果异常已经传播,则异常会转义析构函数
|申请将终止

14
Martin York

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

8
Jeremy

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

7
Harold Ekstrom

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

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

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

4
coppro

否。如果您使用它们并且未通过您的代码调用的代码或代码指定了未指定的异常,则默认行为是立即终止您的程序。

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

4
Ferruccio

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

否则我发现除了throw()之外的任何东西都没有特别有用,表明它不会抛出任何异常。

3
Branan

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

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

3
TheJuice

通常我不会使用异常说明符。但是,如果任何其他例外来自相关函数,该程序肯定无法 更正 ,那么它可能是有用的。在所有情况下,请务必清楚地记录该功能可能出现的异常情况。

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

我还要注意Scott Meyers在更有效的C++中解决了这个问题。他的有效C++和更高效的C++是强烈推荐的书籍。

2
Kris Kumler

是的,如果你是内部文档。或者也许写一个别人会使用的图书馆,这样他们就可以在不查阅文档的情况下告诉会发生什么。投掷或不投掷可以被视为API的一部分,几乎就像返回值一样。

我同意,它们对于在编译器中强制执行Java风格并不是很有用,但它总比没有任何东西或随意的评论更好。

2
user10392

它们可用于单元测试,因此在编写测试时,您知道函数在失败时会发生什么,但在编译器中没有强制执行它们。我认为它们是C++中不需要的额外代码。您选择的所有内容都是您在整个项目和团队成员中遵循相同的编码标准,以便您的代码保持可读性。

2
Odd

来自文章:

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

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

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

0
Marius