​原文​

比较D中的异常和错误

什么是D中​​异常​​​和​​错误​​​?为什么有区别?为什么D认为可以在​​不抛​​​函数内部抛​​错误​​?

抛可抛

简单地说,异常是在正常​​代码​​​中不应出现的​​"异常"​​​情况.​​异常​​​优于​​其他类型​​​的​​错误处理​​​(如返回​​错误码​​​或​​错误/值​​​组合)的原因是:要处理​​异常​​​.你不处理,别人就会处理.并且​​默认​​​是打印出​​异常​​​时的状态,然后​​退出​​​程序.
注意,最新​​​编译器​​​有叫​​@mustUse​​​的要求​​必须​​​处理(可能包含​​错误​​​的)​​返回值​​​的​​新功能​​.

​抛​​​相对昂贵,即应该只在真正​​异常​​​时使用它,而不要用来​​控制​​​流程.
在D中,只需用要求对象为​​​可抛​​​的​​抛​​​语句即可触发​​异常或错误​​​.类似​​C++​​​协程的​​可等待​​.

int div(int x, int y) {
if(y == 0) throw new Exception("除0!");
return x / y;
}

然后可在​​其他​​​地方抓它.抓它时,​​异常​​​包含生成异常的​​文件/行​​​及​​调用栈层次​​​位置等​​异常​​​的所有信息.​​美妙​​​在于可把​​处理器​​​放在​​调用栈​​​上最需要​​处理​​它的地方.

考虑​​Web​​​服务器,你想用​​异常​​​处理器处理​​HTTP​​​请求部分,只需在有​​异常​​​时抛​​异常​​​,并在想要​​处理​​​的​​地方​​​抓它.语言​​负责​​其余工作!

展开栈

语言​​必须​​​处理​​展开栈​​​.如,如果栈上有带​​析构器​​​的结构,则必须调用这些​​析构器​​​,否则程序不完整.如果​​引用计数​​​的​​智能指针​​​在​​抛异常​​​时未减少​​引用​​​.则一直​​锁定​​​着​​互斥锁​​.

还有​​域保护​​​​语句​​​可帮助​​正确​​​设计​​初化/清理​​​代码,而不必在​​域尾​​​或每个​​中​​​语句中​​清理​​​.​​抛异常​​时也必须运行它们.

不抛函数

​不抛​​​函数是不让​​异常​​​逃逸出函数的​​函数​​​.即你​​必须​​​处理所有​​本函数​​​内或调用的​​抛函数​​​内抛的​​异常​​​.​​不抛​​​函数目的是​​通知​​​编译器可省略​​抛异常​​​的​​清理代码​​.

从而​​输出​​​更少代码,更大优化,使​​不抛​​​函数比​​抛函数​​更好.

错误的展开栈

但是,仍允许​​不抛​​​函数抛​​错误​​​.
原理是​​​编译器​​​仍然省略了​​清理异常​​​的代码,而抓到​​错误​​​时,则​​禁止​​​继续跑​​程序​​​.否则,​​程序​​​显然无效.​​抛/抓​​​错误类似​​goto​​语句.

以下示例和输出演示如何跳过​​清理​​代码:

void foo() nothrow {
throw new Error("抓我");
}

void bar() nothrow {
import core.stdc.stdio;
scope(exit) printf("清理...\n");
foo();
}

void main() {
bar();
}
//抓我.
//3,9,13一直到达主函数,并退出.

可抓​​错误​​​,并按​​控制流​​​机制来用.如,想从常见的​​越界访问数组​​​中​​恢复​​​.但是可能未​​正确​​​清理栈帧,即栈上​​未解锁​​​互斥锁或​​引用递减​​等.

总之,程序状态不确定.​​继续​​执行会损坏程序数据,或崩溃应用.

如何处理错误

唯一的​​例外(异常)​​​(​​双关语​​​),是测试代码时.​​单元测试和合同​​​中,语言保证正确展开​​抛断定错误​​​的栈.
根据经验,​​​错误​​​为编程错误(即,程序员犯的错),而​​异常​​​为​​环境/用户​​​错误.
如果抓到错误,正确操作是执行​​​扫尾/清理​​​操作,并确保操作为​​确定​​状态.再退出程序.