提问人:Konstantin 提问时间:6/29/2009 最后编辑:Mark LakataKonstantin 更新时间:11/17/2023 访问量:164077
在函数的签名中抛出关键字
Throw keyword in function's signature
问:
在函数签名中使用 C++ 关键字被认为是不良做法的技术原因是什么?throw
bool some_func() throw(myExc)
{
...
if (problem_occurred)
{
throw myExc("problem occurred");
}
...
}
答:
不,这不被认为是好的做法。相反,这通常被认为是一个坏主意。
http://www.gotw.ca/publications/mill22.htm 更详细地解释了原因,但问题部分是编译器无法强制执行这一点,因此必须在运行时对其进行检查,这通常是不可取的。而且在任何情况下都没有得到很好的支持。(MSVC 忽略异常规范,但 throw() 除外,它将其解释为保证不会引发异常。
评论
当投掷规范被添加到语言中时,它是出于最好的意图,但实践已经证明了一种更实用的方法。
对于 C++,我的一般经验法则是仅使用抛出规范来指示方法不能抛出。这是一个强有力的保证。否则,假设它可以抛出任何东西。
throw 说明符的唯一实际效果是,如果函数抛出的内容与 不同,将被调用(而不是正常的未处理异常机制)。myExc
std::unexpected
为了记录函数可能引发的异常类型,我通常会这样做:
bool
some_func() /* throw (myExc) */ {
}
评论
Jalf 已经链接到它,但 GOTW 很好地说明了为什么异常规范并不像人们希望的那样有用:
int Gunc() throw(); // will throw nothing (?)
int Hunc() throw(A,B); // can only throw A or B (?)
评论正确吗?差一点。 可能确实会扔东西,而且很可能会扔 A 或 B 以外的东西!编译器只是保证如果他们这样做,就会毫无意义地击败他们......哦,大多数时候,你的程序也毫无意义。
Gunc()
Hunc()
这就是归根结底的原因,你可能最终会接到一个电话,你的程序会快速但痛苦地死去。terminate()
GOTW的结论是:
因此,以下是我们作为一个社区迄今为止学到的最好的建议:
- 道德#1:永远不要编写异常规范。
- 道德#2:除了可能是一个空的,但如果我是你,我甚至会避免。
评论
好吧,在谷歌上搜索这个投掷规范时,我看了一下这篇文章:- (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 (...)
{
}
}
};
当编译器看到这一点时,使用 “throw()” 属性,编译器可以完全优化 “bar” 变量,因为它知道无法从 MethodThatCannotThrow() 抛出异常。如果没有 throw() 属性,编译器必须创建“bar”变量,因为如果 MethodThatCannotThrow 抛出异常,异常处理程序可能/将取决于 bar 变量的值。
此外,像 prefast 这样的源代码分析工具可以(并且将)使用 throw() 注解来提高它们的错误检测能力——例如,如果你有一个 try/catch,并且你调用的所有函数都被标记为 throw(),你就不需要 try/catch(是的,如果你以后调用一个可以抛出的函数,这就会有问题)。
为了给这个问题的所有其他答案增加更多的价值,应该在这个问题上花几分钟时间: 以下代码的输出是什么?
#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()
详细信息:调用了第一个函数,但由于它不会重新引发函数原型的匹配异常类型,因此最终会调用该函数。因此,完整的输出如下所示:my_unexpected()
throw_exception()
std::terminate()
user@user:~/tmp$ g++ -o except.test
except.test.cpp user@user:~/tmp$ ./except.test
好吧 - 这是在抛出“int”实例后意外
终止的
已中止(核心转储)
某些编译器可能会使用对仅返回成员变量且不可能引发异常的内联函数的无抛出规范进行悲观化(与优化相反的虚构词),这可能会对性能产生不利影响。这在 Boost 文献中进行了描述:异常规范
对于某些编译器,如果进行了正确的优化,并且使用该函数会以证明其合理性的方式影响性能,则对非内联函数的无抛出规范可能是有益的。
对我来说,这听起来像是是否使用它是一个非常挑剔的眼光,作为性能优化工作的一部分,也许是使用分析工具。
对于那些匆忙的人来说,上面链接中的一句话(包含一个示例,说明从朴素的编译器中指定对内联函数进行抛掷的不良意外影响):
例外规范理由
异常规范 [ISO 15.4] 有时被编码以指示可能引发的异常,或者因为程序员希望它们能够提高性能。但请考虑智能指针中的以下成员:
T&运算符*() const throw() { return *ptr;
此函数不调用其他函数;它只操作基本数据类型,如指针 因此,永远不能调用异常规范的运行时行为。该函数完全暴露给编译器;事实上,它是内联声明的,因此,智能编译器可以很容易地推断出函数无法抛出异常,并根据空的异常规范进行相同的优化。然而,一个“愚蠢”的编译器可能会做出各种悲观情绪。
例如,如果存在异常规范,某些编译器会关闭内联。一些编译器会添加 try/catch 块。这种悲观情绪可能是性能灾难,使代码在实际应用中无法使用。
尽管最初很有吸引力,但例外规范往往会产生需要非常仔细思考才能理解的后果。异常规范的最大问题是,程序员使用它们时,就好像它们具有程序员想要的效果一样,而不是它们实际具有的效果。
非内联函数是“不抛出任何内容”异常规范可能对某些编译器有一些好处的地方。
评论
上一个:在函数的签名中抛出关键字
评论
noexcept