j_k*_*bik 3 c++ templates exception exception-specification
我已经阅读了很多关于(不)使用throw(X)函数签名的论点,我认为它在ISO C++中指定的方式(并在当前编译器中实现)它相当无用.但是为什么编译器不能简单地在编译时强制执行异常正确性呢?
如果我编写包含throw(A,B,C)在其签名中的函数/方法定义,编译器在确定给定函数的实现是否异常正确时应该没有太多问题.这意味着功能体具有
throw的其他比throw A; throw B; throw C;;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){}
}
Run Code Online (Sandbox Code Playgroud)
这将要求所有非C++领域代码都是throw()默认指定的(默认情况下extern "C"应该假定它),或者如果存在一些异常互操作性,那么也应该指定适当的标题(至少对于C++)throw.不这样做可以与在不同编译单元中使用具有不同函数/方法返回类型的头进行比较.虽然它没有产生警告或错误,但显然是错误的 - 并且抛出的异常是签名的一部分,它们也应该匹配.
如果我们强制执行此类约束,则会产生三种影响:
try{}catch块,否则运行时检查需要这些块,从而提高异常处理性能.throw,则大多数代码都不会被编译器添加.如果我们为旧代码使用了一些兼容性编译器标志,它就不会破坏任何东西,但是随着新的异常代码更快,将有一个很好的动机不使用它来编写新代码.
总结一下我的问题:为什么ISO C++不要求这样的强制执行?有没有强大的理由不这样做?我一直认为异常只是另一个函数的返回值,而是一个自动控制的函数,所以你可以避免编写像
std::pair<int, bool> str2int(std::string s);
int str2int(std::string s, bool* ok);
Run Code Online (Sandbox Code Playgroud)
加上额外的自动破坏变量和通过堆栈中的多个函数传播,所以你不需要像这样的代码
int doThis(){
int err=0;
[...]
if ((err = doThat())){
return err;
}
[...]
}
Run Code Online (Sandbox Code Playgroud)
;如果您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
[...]
}
}
Run Code Online (Sandbox Code Playgroud)
和:
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
[...]
}
}
Run Code Online (Sandbox Code Playgroud)
这里我包含了一些编译器不会生成汇编级别的伪代码.如您所见,了解可以获得的异常可以减少代码量.如果我们在这里要销毁一些额外的变量,那么额外的代码会更长.
编译器验证的异常作为函数签名的一部分具有两个(理论上的)优点:编译器优化和编译时错误检查.
在编译器方面,抛出异常类X和类的函数之间有什么区别Y?最终......什么都没有.编译器可以使用X它无法处理的异常类进行什么样的优化Y?除非std::exception是特殊的(并且X是从它派生的,而Y不是),编译器有什么关系?
最终,编译器在优化方面唯一关心的是函数是否会抛出任何异常.这就是为什么C++ 11的标准委员会throw(...)倾向于支持noexcept,这表明该功能不会抛出任何东西.
至于编译时错误检查,Java清楚地显示了它的工作原理.你正在写一个函数,foo.你的设计有它投掷X和Y.其他代码使用foo,并抛出任何foo抛出.但是异常规范没有说"无论foo抛出什么".它必须列出X并Y具体说明.
现在你回去改变foo它不再抛出X,但现在它抛出Z.突然,整个项目停止编译.你现在必须转到抛出任何东西的每个函数,foo只是为了改变它的异常规范来匹配foo.
最终,一个程序员只是举起手来说它会抛出任何异常.当你放弃这样的功能时,事实上承认这个功能弊大于利.
并不是说它们没有用.只是它们的实际使用表明它们通常没用.所以没有意义.
另外,请记住,C++的规范声明没有规范意味着抛出任何东西,而不是任何东西(如在Java中).使用该语言最简单的方法就是这样:不检查.所以会有很多人不想使用它.
许多人不想打扰的功能有什么用处,甚至那些做过的人通常也会感到很悲伤?
| 归档时间: |
|
| 查看次数: |
397 次 |
| 最近记录: |