在函数签名中使用C ++ throw
关键字被视为不良做法的技术原因是什么?
bool some_func() throw(myExc)
{
...
if (problem_occurred)
{
throw myExc("problem occurred");
}
...
}
答案 0 :(得分:113)
不,这不是好习惯。相反,它通常被认为是一个坏主意。
http://www.gotw.ca/publications/mill22.htm详细介绍了原因,但问题部分在于编译器无法强制执行此操作,因此必须在运行时检查,这通常是不合需要的。并且在任何情况下都没有得到很好的支持。 (MSVC忽略了除throw()之外的异常规范,它将其解释为保证不会抛出任何异常。
答案 1 :(得分:48)
Jalf已经链接到了它,但是GOTW非常好地说明了为什么异常规范没有人们希望的那么有用:
int Gunc() throw(); // will throw nothing (?)
int Hunc() throw(A,B); // can only throw A or B (?)
评论是否正确?不完全的。
Gunc()
可能确实会抛出一些东西,Hunc()
可能会抛出A或B以外的东西!编译器只是保证如果他们这样做就打败他们......哦,并且在大多数情况下打败你的程序也毫无意义。
这就是它归结为什么,你可能最终会打电话给terminate()
而你的程序会快速而痛苦地死去。
GOTW的结论是:
所以这就是我们社区今天所学到的最佳建议:
- 道德#1:永远不要编写例外规范。
- 道德#2:除了可能是空的,但如果我是你,我甚至会避免这样做。
答案 2 :(得分:27)
要为这个问题的所有其他答案添加更多的价值,我们应该在问题中投入几分钟: 以下代码的输出是什么?
#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;
}
}
答案:如上所述here,程序调用{{1}},因此不会调用任何异常处理程序。
详细信息:调用第一个std::terminate()
函数,但由于它不会为my_unexpected()
函数原型重新抛出匹配的异常类型,最后会调用throw_exception()
。所以完整的输出看起来像这样:
user @ user:〜/ tmp $ g ++ -o except.test except.test.cpp
user @ user:〜/ tmp $ ./except.test
好吧 - 这是出人意料的 抛出'int'实例后终止调用 中止(核心倾销)
答案 3 :(得分:10)
throw说明符的唯一实际效果是,如果函数抛出与myExc
不同的内容,将调用std::unexpected
(而不是正常的未处理异常机制)。
要记录函数可以抛出的异常类型,我通常会这样做:
bool
some_func() /* throw (myExc) */ {
}
答案 4 :(得分:9)
嗯,在谷歌搜索这个投掷规范时,我看了一下这篇文章: - (http://blogs.msdn.com/b/larryosterman/archive/2006/03/22/558390.aspx)
我也在这里复制它的一部分,以便将来可以使用它,而不管上述链接是否有效。
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 (...)
{
}
}
};
当编译器看到这个时,使用&#34; throw()&#34;属性,编译器可以完全优化&#34; bar&#34;变量离开,因为它知道没有办法从MethodThatCannotThrow()抛出异常。如果没有throw()属性,编译器必须创建&#34; bar&#34;变量,因为如果MethodThatCannotThrow抛出异常,异常处理程序可能/将取决于bar变量的值。
此外,像prefast这样的源代码分析工具可以(并且将会)使用throw()注释来改进它们的错误检测功能 - 例如,如果你有一个try / catch,你调用的所有函数都被标记为throw (),你不需要try / catch(是的,如果你以后调用可以抛出的函数,这会有问题。)
答案 5 :(得分:8)
当投掷规格添加到语言中时,它是出于最好的意图,但是练习已经证明了一种更实用的方法。
使用C ++,我的一般经验法则是仅使用throw规范来指示方法不能抛出。这是一个有力的保证。否则,假设它可以抛出任何东西。
答案 6 :(得分:3)
内联函数的无抛出规范只返回一个成员变量并且不可能抛出异常,一些编译器可能会使用它来执行 pessimizations (与优化相反的组成单词) )可能对性能产生不利影响。这在Boost文献中有所描述:Exception-specification
使用一些编译器如果进行了正确的优化,并且使用该函数会以一种合理的方式影响性能,那么非内联函数的无抛出规范可能是有益的。
对我而言,听起来好像是否使用它是一个非常关键的眼睛,作为性能优化工作的一部分,可能使用分析工具。
上述链接引用了那些匆忙的内容(包含一个在天然编译器的内联函数中指定 throw 的意外影响的示例):
例外 - 规范基本原理
异常规范[ISO 15.4]有时会被编码以指示可能抛出的异常,或者因为程序员希望它们能够提高性能。但请从智能指针中考虑以下成员:
T&安培; operator *()const throw(){return * ptr; }
此函数不调用其他函数;它只操纵指针等基本数据类型因此,不能调用异常规范的运行时行为。该函数完全暴露给编译器;实际上它是内联声明的因此,智能编译器可以很容易地推断出函数不能抛出异常,并根据空的异常规范进行相同的优化。 A&#34;哑巴&#34;但是,编译器可能会产生各种各样的悲观情绪。
例如,如果存在异常规范,某些编译器会关闭内联。一些编译器添加了try / catch块。这种悲观情绪可能是一种性能灾难,使得代码在实际应用中无法使用。
虽然最初很有吸引力,但异常规范往往会产生需要非常仔细思考的后果。异常规范的最大问题是程序员使用它们就好像它们具有程序员想要的效果,而不是它们实际具有的效果。
非内联函数是一个&#34;什么都不抛出#34;异常规范可能对某些编译器有一些好处。