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

发布于 2024-07-05 00:41:07 字数 539 浏览 8 评论 0原文

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

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

In C++, you can specify that a function may or may not throw an exception by using an exception specifier. For example:

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

I'm doubtful about actually using them because of the following:

  1. The compiler doesn't really enforce exception specifiers in any rigorous way, so the benefits are not great. Ideally, you would like to get a compile error.
  2. If a function violates an exception specifier, I think the standard behaviour is to terminate the program.
  3. In VS.Net, it treats throw(X) as throw(...), so adherence to the standard is not strong.

Do you think exception specifiers should be used?
Please answer with "yes" or "no" and provide some reasons to justify your answer.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(14

〃温暖了心ぐ 2024-07-12 00:41:08

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

Exception specifications = rubbish, ask any Java developer over the age of 30

无语# 2024-07-12 00:41:08

来自文章:

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

“众所周知,不可能
编写异常安全的泛型
容器。” 这种说法经常听到
参考汤姆的文章
嘉吉 [4] 在其中他探索了
异常安全问题
通用堆栈模板。 在他的
文章中,嘉吉提出了许多有用的
问题,但不幸的是未能
提出他的问题的解决方案。1 他
最后建议
解决方案可能无法实现。
不幸的是,他的文章被阅读了
许多人将其视为这种猜测的“证据”。
自发布以来已有
许多异常安全的例子
通用组件,其中包括 C++
标准库容器。

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

From the article:

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.

And indeed I can think of ways to make template classes exception safe. Unless you don't have control over all the sub-classes then you may have a problem anyway. To do this one could create typedefs in your classes that define the exceptions thrown by various template classes. This think the problem is as always tacking it on afterwards as opposed to designing it in from the start, and I think it's this overhead that's the real hurdle.

临风闻羌笛 2024-07-12 00:41:08

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

They can be useful for unit testing so that when writing tests you know what to expect the function to throw when it fails, but there is no enforcement surrounding them in the compiler. I think that they are extra code that is not necessary in C++. Which ever you choose all that you should be sure of is that you follow the same coding standard across the project and the team members so that your code remains readable.

禾厶谷欠 2024-07-12 00:41:08

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

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

我还要指出,Scott Meyers 在《更有效的 C++》中讨论了这个主题。 他的《Effective C++》和《More Effect C++》是强烈推荐的书籍。

Generally I would not use exception specifiers. However, in cases where if any other exception were to come from the function in question that the program would definitively be unable to correct, then it can be useful. In all cases, make sure to document clearly what exceptions could be expected from that function.

Yes, the expected behavior of a non-specified exception being thrown from a function with exception specifiers is to call terminate().

I will also note that Scott Meyers addresses this subject in More Effective C++. His Effective C++ and More Effective C++ are highly recommended books.

怕倦 2024-07-12 00:41:08

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

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

A "throw()" specification allows the compiler to perform some optimisations when doing code flow analysis if it know that function will never throw an exception (or at least promises to never throw an exception). Larry Osterman talks about this briefly here:

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

毅然前行 2024-07-12 00:41:08

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

请参阅 Herb Sutter 的“异常规范的实用视角”

Avoid exception specifications in C++. The reasons you give in your question are a pretty good start for why.

See Herb Sutter's "A Pragmatic Look at Exception Specifications".

丶视觉 2024-07-12 00:41:08

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

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

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

例外保证

无保证:

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

基本保证:

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

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

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

无投掷保证:

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

I think the standardly except convention (for C++)
Exception specifiers were an experiment in the C++ standard that mostly failed.
The exception being that the no throw specifier is useful but you should also add the appropriate try catch block internally to make sure the code matches the specifier. Herb Sutter has a page on the subject. Gotch 82

In a addition I think it is worth describing Exception Guarantees.

These are basically documentation on how the state of an object is affected by exceptions escaping a method on that object. Unfortunately they are not enforced or otherwise mentioned by the compiler.
Boost and Exceptions

Exception Guarantees

No Guarantee:

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.

Basic Guarantee:

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.

Strong Guarantee: (aka Transactional Guarantee)

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

No Throw Guarantee:

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

盛夏尉蓝 2024-07-12 00:41:08

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

gcc will emit warnings when you violate exception specifications. What I do is to use macros to use the exception specifications only in a "lint" mode compile expressly for checking to make sure the exceptions agree with my documentation.

深海不蓝 2024-07-12 00:41:08

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

The only useful exception specifier is "throw()", as in "doesn't throw".

打小就很酷 2024-07-12 00:41:08

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

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

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

Exception specifications are not wonderfully useful tools in C++. However, there /is/ a good use for them, if combined with std::unexpected.

What I do in some projects is code with exception specifications, and then call set_unexpected() with a function that will throw a special exception of my own design. This exception, upon construction, gets a backtrace (in a platform-specific manner) and is derived from std::bad_exception (to allow it to be propagated if desired). If it causes a terminate() call, as it usually does, the backtrace is printed by what() (as well as the original exception that caused it; not to hard to find that) and so I get information of where my contract was violated, such as what unexpected library exception was thrown.

If I do this, I never allow propagation of library exceptions (except std ones) and derive all my exceptions from std::exception. If a library decides to throw, I will catch and convert into my own hierarchy, allowing for me to always control the code. Templated functions that call dependent functions should avoid exception specifications for obvious reasons; but it's rare to have a templated function interface with library code anyway (and few libraries really use templates in a useful manner).

爱的那么颓废 2024-07-12 00:41:08

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

否则,我认为使用除了 throw() 之外的任何东西来指示它不会抛出任何异常都没有特别有用。

If you're writing code that will be used by people that would rather look at the function declaration than any comments around it, then a specification will tell them which exceptions they might want to catch.

Otherwise I don't find it particularly useful to use anything but throw() to indicate that it doesn't throw any exceptions.

电影里的梦 2024-07-12 00:41:08

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

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

Yes, if you're into internal documentation. Or maybe writing a libary that others will use, so that they can tell what happens without consulting the documentation. Throwing or not throwing can be considered part of the API, almost like the return value.

I agree, they are not really useful for enforcing correctness Java style in the compiler, but it's better than nothing or haphazard comments.

谈情不如逗狗 2024-07-12 00:41:08

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

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

No. If you use them and an exception is thrown that you did not specify, either by your code or code called by your code, then the default behavior is to promptly terminate your program.

Also, I believe their use has been deprecated in current drafts of the C++0x standard.

风情万种。 2024-07-12 00:41:07

不。

以下是几个示例:

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

    模板 
      无效 f( T k ) 
      { 
           T x( k ); 
           xx(); 
      } 
      

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

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

    virtual void open() throw( FileNotFound ); 
      

    可能会演变成

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

    你真的可以把它写成

    <前><代码>抛出(...)

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

  3. 旧代码

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

    int lib_f(); 
    
      无效 g() 抛出( k_too_small_exception ) 
      {  
         int k = lib_f(); 
         if( k < 0 ) 抛出 k_too_small_exception(); 
      } 
      

    lib_f() 抛出异常时,

    g 将终止。 (在大多数情况下)这并不是您真正想要的。 永远不应该调用 std::terminate() 。 让应用程序因未处理的异常而崩溃(您可以从中检索堆栈跟踪)总是比默默地/暴力地死掉要好。

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

    错误 e = open( "bla.txt" ); 
      if( e == FileNotFound ) 
          MessageUser("找不到文件 bla.txt"); 
      if( e == 访问被拒绝 ) 
          MessageUser("无法打开bla.txt,因为我们没有读取权限..."); 
      if( e != 成功 ) 
          MessageUser( "由于其他错误而失败,错误代码 = " + itoa( e ) ); 
    
      尝试 
      { 
         std::vector   k(1000); 
         // ... 
      } 
      catch( const bad_alloc& b ) 
      {  
         MessageUser("内存不足,退出进程"); 
         扔; 
      } 
      

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

No.

Here are several examples why:

  1. Template code is impossible to write with exception specifications,

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

    The copies might throw, the parameter passing might throw, and x() might throw some unknown exception.

  2. Exception-specifications tend to prohibit extensibility.

    virtual void open() throw( FileNotFound );
    

    might evolve into

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

    You could really write that as

    throw( ... )
    

    The first is not extensible, the second is overambitious and the third is really what you mean, when you write virtual functions.

  3. Legacy code

    When you write code which relies on another library, you don't really know what it might do when something goes horribly wrong.

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

    g will terminate, when lib_f() throws. This is (in most cases) not what you really want. std::terminate() should never be called. It is always better to let the application crash with an unhandled exception, from which you can retrieve a stack-trace, than to silently/violently die.

  4. Write code that returns common errors and throws on exceptional occasions.

    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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文