我应该在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
由于以下原因,我对实际使用它们表示怀疑:
- 编译器实际上并没有以任何严格的方式强制执行异常说明符,因此好处并不大。理想情况下,我们希望获得一个编译错误。
- 如果一个函数违反了异常说明符,我认为标准行为是终止程序。
- 在VS.Net中,它将throw(X)视为throw(...),因此对标准的遵循性不强。
我们认为应该使用异常说明符吗?
请回答"是"或者"否",并提供一些理由来证明回答合理。
解决方案
通常,我不会使用异常说明符。但是,如果该功能有其他异常导致程序肯定无法更正,则此功能很有用。在所有情况下,请确保清楚地记录该功能可能发生的异常情况。
是的,带有异常说明符的函数抛出的未指定异常的预期行为是调用terminate()。
我还将注意到,斯科特·迈耶斯(Scott Meyers)在《更有效的C ++》中解决了这个问题。强烈推荐他的有效C ++和更有效C ++。
如果我们要编写的代码供人们使用,而他们宁愿看一下函数声明而不是周围的任何注释,那么规范将告诉他们可能想要捕获的异常。
否则,我认为使用throw()
以外的任何东西来表示它不会抛出任何异常并不是特别有用。
是的,如果我们需要内部文档。或者也许写一个别人可以使用的库,这样他们就可以知道发生了什么而无需查阅文档。抛出或者不抛出都可以视为API的一部分,就像返回值一样。
我同意,它们对于在编译器中强制正确性Java样式并不是真正有用,但是总比没有注释或者随意注释好。
避免使用C ++中的异常规范。我们提出问题的原因是为什么的一个很好的开始。
参见Herb Sutter的"对异常规范的实用观察"。
它们对于单元测试很有用,因此在编写测试时,我们知道该函数在失败时会抛出什么结果,但是在编译器中没有强制执行的功能。我认为它们是C ++中不必要的额外代码。我们应该确保选择哪种方法,就是在项目和团队成员之间遵循相同的编码标准,从而使代码保持可读性。
不。
这是为什么的几个示例:
- 模板代码无法使用异常规范来编写,
template<class T> void f( T k ) { T x( k ); x.x(); }
副本可能会抛出,传递的参数可能会抛出,而x()可能会抛出一些未知异常。
- 异常规范倾向于禁止可扩展性。
virtual void open() throw( FileNotFound );
可能演变成
virtual void open() throw( FileNotFound, SocketNotReady, InterprocessObjectNotImplemented, HardwareUnresponsive );
你真的可以写成
throw( ... )
第一个是不可扩展的,第二个是过于雄心勃勃的,第三个实际上是我们在编写虚拟函数时的意思。
- 遗留代码当我们编写依赖于另一个库的代码时,我们实际上并不知道当出现严重错误时它会做什么。
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()
。总是让应用程序因未处理的异常而崩溃(从中可以检索堆栈跟踪)总是比静默/剧烈地崩溃更好。
- 编写返回常见错误并在特殊情况下抛出的代码。
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; }
但是,当库仅引发我们自己的异常时,我们可以使用异常规范说明意图。
当我们违反异常规范时,gcc将发出警告。我要做的是仅在" lint"模式下使用宏来使用异常规范,以明确进行检查以确保异常与我的文档一致。
我认为除约定外(对于C ++)
异常说明符是C ++标准中的一项实验,多数失败。
唯一的例外是,不抛出说明符很有用,但我们还应该在内部添加适当的try catch块,以确保代码与说明符匹配。 Herb Sutter在此主题上有一页。戈特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
唯一有用的异常说明符是" throw()",如"不抛出"。
否。如果使用它们,并且引发了未指定的异常(无论是通过代码还是由代码调用的代码),则默认行为是立即终止程序。
另外,我相信在C ++ 0x标准的当前草案中已不赞成使用它们。
异常规范不是C ++中非常有用的工具。但是,如果将它们与std :: unexpected结合使用,则/ is /会很好地使用它们。
在某些项目中,我要做的是编写具有异常规范的代码,然后使用将引发我自己设计的特殊异常的函数调用set_unexpected()。构造后,此异常获取回溯(以特定于平台的方式),并从std :: bad_exception派生(如果需要,可以传播该异常)。如果它像通常那样引起了一个terate()调用,则回溯将由what()打印(以及引起它的原始异常;不难发现),因此我获得了我的合同在哪里的信息违反,例如引发了意外的库异常。
如果这样做,我将永远不允许传播库异常(std异常除外),并从std :: exception派生我的所有异常。如果库决定抛出,则我将捕获并转换为自己的层次结构,从而使我能够始终控制代码。由于明显的原因,调用依赖函数的模板化函数应避免指定异常;但是无论如何,很少有带有库代码的模板化函数接口(很少有库确实以有用的方式使用模板)。
异常规范=垃圾,请问30岁以上的任何Java开发人员
从文章:
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,以定义各种模板类引发的异常。这种想法是一如既往地解决问题,而不是一开始就进行设计,而我认为,这是真正的障碍。
如果" throw()"规范知道函数永远不会抛出异常(或者至少承诺永远不会抛出异常),则" throw()"规范允许编译器在进行代码流分析时执行一些优化。拉里·奥斯特曼(Larry Osterman)在这里简短地谈到了这一点:
http://blogs.msdn.com/larryosterman/archive/2006/03/22/558390.aspx