【发布时间】:2012-07-05 22:29:43
【问题描述】:
我已经阅读了很多关于(不)在函数签名中使用 throw(X) 的论点,我认为它在 ISO C++ 中指定的方式(并在当前编译器中实现)是相当没用的。但是为什么编译器不能在编译时简单地enforce异常正确性呢?
如果我在其签名中编写包含throw(A,B,C) 的函数/方法定义,编译器在确定给定函数的实现是否异常正确时应该不会有很多问题。这意味着函数体有
- 除了
throw A; throw B; throw C;之外没有throw; - 没有比
throw (A,B,C)更少限制的函数/方法调用;
,至少在try{}catch() 之外捕获其他抛出的类型。如果在不满足这些要求的情况下编译器引发错误,那么所有函数都应该是“安全的”,并且不需要像 unexpected() 这样的运行时函数。所有这些都将在编译时得到保证。
void fooA() throw (A){
}
void fooAB() throw (A,B){
}
void fooABC() throw (A,B,C){
}
void bar() throw (A){
throw A(); // ok
throw B(); // Compiler error
fooA(); // ok
fooAB(); // compiler error
fooABC(); // compiler error
try{
throw A(); // ok
throw B(); // ok
throw C(); // Compiler error
fooA(); // ok
fooAB(); // ok
fooABC(); // compiler error
} catch (B){}
}
这将要求所有非 C++ 领域代码默认指定 throw()(extern "C" 应默认假定它),或者如果存在一些异常互操作性,则应使用适当的标头(至少对于 C++) throw-也指定了。不这样做可以与在不同的编译单元中使用具有不同函数/方法返回类型的头文件进行比较。虽然它没有产生警告或错误,但它显然是错误的 - 由于抛出的异常是签名的一部分,它们也应该匹配。
如果我们强制执行这样的限制,它会产生三个效果:
- 它将删除所有那些隐含的
try{}catch块,否则运行时检查需要这些块,从而提高异常处理性能。 - “异常使我们的库太大,因此我们将其关闭”的论点;会消失,因为大多数附加代码都存在于每个函数调用中那些不必要的隐式 throw/catch 指令中。如果代码是正确的
throw-specified,大部分代码不会被编译器添加。 - 这会让编程界的大多数人大发雷霆,因为似乎没有人喜欢 例外。现在,由于这些实际上是可用的,我们需要学习如何使用它们。
如果我们对旧代码使用一些兼容性编译器标志,它不会破坏任何东西,但是由于新的异常代码会更快,所以不使用它编写新代码是一个很好的动机。
总结一下我的问题:为什么 ISO C++ 不需要这种强制措施?有什么强有力的理由让它不存在吗?我一直认为异常只是另一个函数的返回值,但它是自动管理的,所以你可以避免编写类似的函数
std::pair<int, bool> str2int(std::string s);
int str2int(std::string s, bool* ok);
加上额外的变量自动销毁和通过堆栈上的多个函数传播,因此您不需要像这样的代码
int doThis(){
int err=0;
[...]
if ((err = doThat())){
return err;
}
[...]
}
;如果你可以要求return的函数只有正确的类型,你为什么不能要求throw呢?
为什么异常说明符不能更好?为什么它们不像我从一开始就描述的那样制作?
PS 我知道例外和模板可能存在一些问题 - 根据这个问题的答案,也许我会问另一个问题 - 现在让我们忘记模板。
编辑(回应@NicolBolas):
编译器可以对异常类 X 进行哪些优化,而对 Y 却无法做到这一点?
比较:
void fooA() throw (A){
}
void fooAB() throw (A,B){
}
void fooABC() throw (A,B,C){
}
void bar() throw (){
try{
fooA();
// if (exception == A) goto A_catch
fooAB();
// if (exception == A) goto A_catch
// if (exception == B) goto B_catch
fooABC();
// if (exception == A) goto A_catch
// if (exception == B) goto B_catch
// if (exception == C) goto C_catch
}
catch (A){ // :A_catch
[...]
}
catch (B){ // :B_catch
[...]
}
catch (C){ // :C_catch
[...]
}
}
和:
void fooA(){
}
void fooAB(){
}
void fooABC(){
}
void bar(){
try{
fooA();
// if (exception == A) goto A_catch;
// if (exception == B) goto B_catch;
// if (exception == C) goto C_catch;
// if (exception == other) return exception;
fooAB();
// if (exception == A) goto A_catch;
// if (exception == B) goto B_catch;
// if (exception == C) goto C_catch;
// if (exception == other) return exception;
fooABC();
// if (exception == A) goto A_catch;
// if (exception == B) goto B_catch;
// if (exception == C) goto C_catch;
// if (exception == other) return exception;
}
catch (A){ // :A_catch
[...]
}
catch (B){ // :B_catch
[...]
}
catch (C){ // :C_catch
[...]
}
}
在这里,我包含了一些编译器不会生成汇编级别的伪代码。如您所见,知道您可以获得哪些异常可以减少代码量。如果我们在这里有一些额外的变量要销毁,额外的代码会更长。
【问题讨论】:
-
我们很高兴 C++ 不是 Java 并且没有引入检查异常。
-
只有愚蠢的游戏开发者会因为性能而关闭核心语言功能。如果没有广泛的语言支持,受检查的异常很烦人(不,Java 级别还不够),而且 C++ 已经足够复杂了。
-
是的,不惹恼程序员应该是语言设计的标准。
-
@j_kubik 你认为 Java 中的人是做什么的?
throws Exception通常是函数签名的一部分,因为这个特性非常烦人。或者有时他们只是try { ... } catch(Exception) { log_and_forget(); },这更糟。 这实际上是某些 IDE 默认生成的,作为编译器错误的修复程序。真是太糟糕了。程序员在遇到检查异常时会做这些事情,因为至少在 Java 中指定时(这与您在此处的描述非常相似)它们会损害生产力,并且 这些“修复”只会使问题变得更糟 i>. -
仅当您假设所有未手动将所有异常从 throw 站点传播到 catch 站点的代码都不是好代码时。如果你这样做,我不可能让我的观点对你有说服力。
标签: c++ templates exception exception-specification