在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
由于以下因素,我对实际使用它们表示怀疑:
您认为应该使用异常说明符吗? 请回答“是”或“否”并提供一些理由来证明您的答案。
答案 0 :(得分:91)
没有
以下是几个例子:
使用例外规范无法编写模板代码,
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();
}
当g
抛出时, lib_f()
将终止。这(在大多数情况下)不是你真正想要的。永远不应该调用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;
}
尽管如此,当您的库只是抛出自己的异常时,您可以使用异常规范来表明您的意图。
答案 1 :(得分:42)
避免使用C ++中的异常规范。你在问题中提出的原因是一个很好的开始。
见Herb Sutter的"A Pragmatic Look at Exception Specifications"。
答案 2 :(得分:14)
我认为标准除了惯例(对于C ++)
异常说明符是C ++标准中的一个实验,大多数都失败了
例外情况是,无抛出说明符很有用,但您还应在内部添加适当的try catch块以确保代码与说明符匹配。 Herb Sutter有一个关于这个主题的页面。 Gotch 82
另外我认为值得描述异常保证。
这些基本上是关于如何通过转义该对象上的方法的异常影响对象状态的文档。不幸的是,编译器没有强制执行或以其他方式提及它们 Boost and Exceptions
在异常转义方法后,无法保证对象的状态 在这些情况下,不应再使用该对象。
在几乎所有情况下,这应该是方法提供的最低保证 这可以保证对象的状态定义良好,并且仍可以一直使用。
这可以保证该方法成功完成 或者抛出异常并且对象状态不会改变。
该方法保证不允许任何异常传播出该方法 所有的破坏者都应该做出这种保证 |注:如果异常已经传播,则异常会转义析构函数 |申请将终止
答案 3 :(得分:8)
当您违反例外规范时,gcc会发出警告。我所做的是使用宏仅在“lint”模式下使用异常规范,以便检查以确保异常与我的文档一致。
答案 4 :(得分:7)
唯一有用的异常说明符是“throw()”,如“不抛出”。
答案 5 :(得分:4)
没有。如果您使用它们并且未通过您的代码调用的代码或代码指定了未指定的异常,则默认行为是立即终止您的程序。
另外,我相信它们的使用在C ++ 0x标准的当前草案中已被弃用。
答案 6 :(得分:4)
异常规范在C ++中并不是非常有用的工具。但是,如果与std :: unexpected。
结合使用,它们/它们是很好用的我在某些项目中所做的是具有异常规范的代码,然后使用一个函数调用set_unexpected(),该函数会抛出我自己设计的特殊异常。这个异常在构造时会得到一个回溯(以特定于平台的方式),并从std :: bad_exception派生(允许它在需要时传播)。如果它像通常那样导致terminate()调用,则回溯由what()(以及导致它的原始异常;不难找到)打印,因此我得到了我的合同所在的信息违反了,例如抛出了意外的库异常。
如果我这样做,我从不允许传播库异常(除了std之外)并从std :: exception派生所有异常。如果一个库决定抛出,我将捕获并转换为我自己的层次结构,允许我始终控制代码。调用依赖函数的模板函数应该出于明显的原因避免异常规范;但是很少有一个带有库代码的模板化函数接口(很少有库真正以有用的方式使用模板)。
答案 7 :(得分:3)
如果您编写的代码将由人们使用,而不是查看函数声明而不是围绕它的任何注释,那么规范将告诉他们可能想要捕获哪些异常。
否则我发现使用throw()
以外的任何东西来表示它不会抛出任何异常都没有特别有用。
答案 8 :(得分:3)
“throw()”规范允许编译器在进行代码流分析时执行一些优化,如果它知道函数永远不会抛出异常(或至少承诺永不抛出异常)。 Larry Osterman在这里简要地谈到了这一点:
http://blogs.msdn.com/larryosterman/archive/2006/03/22/558390.aspx
答案 9 :(得分:2)
通常我不会使用异常说明符。但是,如果任何其他异常来自相关函数,该程序肯定无法更正,那么它可能是有用的。在所有情况下,请务必清楚地记录该功能可以预期的异常情况。
是的,从具有异常说明符的函数抛出的非指定异常的预期行为是调用terminate()。
我还要注意Scott Meyers在更有效的C ++中解决了这个问题。他的有效C ++和更高效的C ++是强烈推荐的书籍。
答案 10 :(得分:2)
是的,如果你是内部文档。或者也许写一个别人会使用的图书馆,这样他们就可以在不查阅文档的情况下告诉会发生什么。投掷或不投掷可以被视为API的一部分,几乎就像返回值。
我同意,它们对于在编译器中强制执行Java风格并不是很有用,但它总比没有任何东西或随意的评论更好。
答案 11 :(得分:2)
它们可用于单元测试,因此在编写测试时,您知道函数在失败时会发生什么,但在编译器中没有强制执行它们。我认为它们是C ++中不需要的额外代码。您选择的所有内容都是您在整个项目和团队成员中遵循相同的编码标准,以便您的代码保持可读性。
答案 12 :(得分:0)
来自文章:
http://www.boost.org/community/exception_safety.html
“众所周知,这是不可能的 编写一个异常安全的泛型 容器。“经常听到这种说法 参考汤姆的一篇文章 嘉吉[4]在其中探索了 一个例外安全问题 通用堆栈模板。在他的 文章,嘉吉提出了很多有用的东西 问题,但遗憾的是没有 解决他的问题。他 通过建议a得出结论 解决方案可能无法实现。 不幸的是,他的文章被阅读了 许多人作为这种猜测的“证据”。 自发布以来就有了 许多异常安全的例子 通用组件,其中包括C ++ 标准库容器。
事实上,我可以想办法使模板类异常安全。除非您无法控制所有子类,否则您可能会遇到问题。为此,可以在类中创建typedef,以定义各种模板类抛出的异常。我认为这个问题总是在事后处理,而不是从一开始就设计它,而且我认为这是开销,这是真正的障碍。
答案 13 :(得分:-1)
异常规范=垃圾,请问任何年龄超过30岁的Java开发人员