抛出函数签名中的关键字

为什么在函数签名中使用C ++ throw关键字被认为是不好的做法是什么技术原因?

 bool some_func() throw(myExc) { ... if (problem_occurred) { throw myExc("problem occurred"); } ... } 

不,这不被认为是好的做法。 相反,这通常被认为是一个坏主意。

http://www.gotw.ca/publications/mill22.htm详细介绍了为什么,但问题部分在于编译器无法执行此操作,所以必须在运行时进行检查,这通常是不可取的。; 在任何情况下都不是很好的支持。 (MSVC忽略除throw()之外的exception规范,它解释为保证不会抛出exception。

Jalf已经把它和它联系起来了,但是GOTW很好地说明了为什么exception规范不如人们所希望的那样有用:

 int Gunc() throw(); // will throw nothing (?) int Hunc() throw(A,B); // can only throw A or B (?) 

评论是否正确? 不完全的。 Gunc()确实可以抛出一些东西,而Hunc()可能抛出A或B以外的东西! 编译器只是保证打败他们毫无意义,如果他们这样做…哦,并且在大多数情况下也打败你的程序。

这就是事情的真相,你可能最终只会打一个电话给terminate() ,你的程序快死了但是痛苦不堪。

GOTW的结论是:

所以,这就是我们作为一个社区迄今所学到的最好的build议:

  • 道德#1:永远不要写一个exception规范。
  • 道德#2:除了可能是一个空的,但如果我是你,我甚至会避免。

为了给这个问题的所有其他答案增加一些更多的价值,应该在这个问题上投入几分钟的时间:以下代码的输出是什么?

 #include <iostream> void throw_exception() throw(const char *) { throw 10; } void my_unexpected(){ std::cout << "well - this was unexpected" << std::endl; } int main(int argc, char **argv){ std::set_unexpected(my_unexpected); try{ throw_exception(); }catch(int x){ std::cout << "catch int: " << x << std::endl; }catch(...){ std::cout << "catch ..." << std::endl; } } 

答:如上所述,程序调用std :: terminate(),因此没有任何exception处理程序会被调用。 详细信息:首先调用my_unexpected()函数,但由于它不会为throw_exception()函数原型重新抛出匹配的exceptiontypes,因此最终会调用std :: terminate()。 所以完整的输出如下所示:

user @ user:〜/ tmp $ g ++ -o except.test except.test.cpp
user @ user:〜/ tmp $ ./except.test
好 – 这是意想不到的
抛出'int'实例后终止调用
中止(核心倾弃)

throw说明符的唯一实际效果是,如果函数抛出与myExc不同的东西,则会调用std::unexpected (而不是正常的未处理的exception机制)。

要logging函数可以抛出的exception,我通常这样做:

 bool some_func() /* throw (myExc) */ { } 

当抛出规范被添加到语言时,它的意图是最好的,但实践已经证实了一个更实际的方法。

用C ++,我的一般经验法则是只使用throw规范来表示一个方法不能抛出。 这是一个有力的保证。 否则,假设它可以抛出任何东西。

那么,在使用这个throw规范的时候,我看了这篇文章: – ( http://blogs.msdn.com/b/larryosterman/archive/2006/03/22/558390.aspx

我也在这里再现了它的一部分,以便将来可以使用,而不pipe上述链接是否有效。

  class MyClass { size_t CalculateFoo() { : : }; size_t MethodThatCannotThrow() throw() { return 100; }; void ExampleMethod() { size_t foo, bar; try { foo = CalculateFoo(); bar = foo * 100; MethodThatCannotThrow(); printf("bar is %d", bar); } catch (...) { } } }; 

当编译器看到这个时,使用“throw()”属性,编译器可以完全优化“bar”variables,因为它知道无法从MethodThatCannotThrow()抛出exception。 如果没有throw()属性,编译器必须创build“bar”variables,因为如果MethodThatCannotThrow引发exception,exception处理程序可能会取决于barvariables的值。

此外,像prefast这样的源代码分析工具可以(也将会)使用throw()注释来提高错误检测能力 – 例如,如果您有try / catch,并且您调用的所有函数都标记为throw()你不需要try / catch(是的,如果你以后调用一个可能会抛出的函数,这会产生问题)。

对于只返回成员variables且不可能抛出exception的内联函数的无抛出规范可能会被某些编译器用于优化(与优化相反),从而对性能产生不利影响。 这在Boost文献中有描述: exception规范

对于一些编译器,如果进行了正确的优化,并且使用该函数会影响性能,则非内联函数的非抛出规范可能会有所帮助。

对我来说,听起来是否使用它是由非常关键的眼睛作为性能优化工作的一部分,可能使用分析工具。

上面的链接引用了这些匆忙:

exception规范的基本原理

例外规范[ISO 15.4]有时会被编码,以表明可能抛出什么exception,或者因为程序员希望他们会提高性能。 但是从智能指针考虑以下成员:

T&operator *()const throw(){return * ptr; }

这个函数不会调用其他函数。 它只能操作像指针这样的基本数据types。因此,不能调用exception规范的运行时行为。 该函数完全暴露给编译器; 事实上,它是内联的。因此,一个聪明的编译器可以很容易地推断出这些函数不能抛出exception,并根据空的exception规范进行相同的优化。 然而,一个“愚蠢”的编译器可能会做出各种悲观。

例如,如果有一个exception规范,一些编译器会closures内联。 一些编译器添加try / catch块。 这种悲观性可能是性能灾难,使得代码在实际应用中不可用。

虽然最初是有吸引力的,但是exception规范往往会产生需要非常仔细的思考才能理解的后果。 exception规范的最大问题在于程序员使用它们,好像它们具有程序员想要的效果,而不是实际上具有的效果。

一个非内联函数是一个“不抛出”exception规范可能会有一些编译器的好处。