关于函数:我应该在C ++中使用异常说明符吗?

关于函数:我应该在C ++中使用异常说明符吗?

Should I use an exception specifier in C++?

在C ++中,您可以通过使用异常说明符指定函数可能会也可能不会抛出异常。 例如:

1
2
3
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

由于以下因素,我对实际使用它们表示怀疑:

  • 编译器并没有以任何严格的方式真正强制执行异常说明符,因此效益并不高。 理想情况下,您希望得到编译错误。
  • 如果函数违反了异常说明符,我认为标准行为是终止程序。
  • 在VS.Net中,它将throw(X)视为throw(...),因此遵守标准并不强。
  • 你认为应该使用异常说明符吗?
    请回答"是"或"否"并提供一些理由来证明您的答案。


    没有。

    以下是几个例子:

  • 使用异常规范无法编写模板代码,

    1
    2
    3
    4
    5
    6
    template<class T>
    void f( T k )
    {
         T x( k );
         x.x();
    }

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

  • 异常规范往往会禁止可扩展性。

    1
    virtual void open() throw( FileNotFound );

    可能演变成

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

    你真的可以这样写

    1
    throw( ... )

    第一个是不可扩展的,第二个是过于雄心勃勃的,第三个是你在写虚拟函数时的意思。

  • 遗留代码

    当你编写依赖于另一个库的代码时,你真的不知道当出现可怕的错误时它会做什么。

    1
    2
    3
    4
    5
    6
    7
    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()。最好让应用程序崩溃时出现未处理的异常,从中可以检索堆栈跟踪,而不是静默/暴力死亡。

  • 编写返回常见错误的代码,并在特殊情况下抛出。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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;
    }
  • 然而,当您的库只是抛出自己的异常时,您可以使用异常规范来陈述您的意图。


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

    参见Herb Sutter的"实用的异常规范"。


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

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

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

    例外保证

    无保证:

    There is no guarantee about the state of the object after an exception escapes a method
    In these situations the object should no longer be used.

    基本保证:

    In nearly all situations this should be the minimum guarantee a method provides.
    This guarantees the object's state is well defined and can still be consistently used.

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

    This guarantees that the method will complete successfully
    Or an Exception will be thrown and the objects state will not change.

    无投保证:

    The method guarantees that no exceptions are allowed to propagate out of the method.
    All destructors should make this guarantee.
    | N.B. If an exception escapes a destructor while an exception is already propagating
    | the application will terminate


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


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


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

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

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


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

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


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

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


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

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


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


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

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


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

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

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


    来自文章:

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

    "It is well known to be impossible to
    write an exception-safe generic
    container." This claim is often heard
    with reference to an article by Tom
    Cargill [4] in which he explores the
    problem of exception-safety for a
    generic stack template. In his
    article, Cargill raises many useful
    questions, but unfortunately fails to
    present a solution to his problem.1 He
    concludes by suggesting that a
    solution may not be possible.
    Unfortunately, his article was read by
    many as"proof" of that speculation.
    Since it was published there have been
    many examples of exception-safe
    generic components, among them the C++
    standard library containers.

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


    例外规范=垃圾,问任何30岁以上的Java开发人员


    推荐阅读