上节课,我建议尽量不用裸指针、new 和 delete,因为它们很危险,容易导致严重错误。这就引出了一个问题,如何正确且优雅地处理运行时的错误。
实际上,想要达成这个目标,还真不是件简单的事情。
程序在运行的时候不可能“一帆风顺”,总会遇到这样那样的内外部故障,而我们写程序的人就要尽量考虑周全,准备各种“预案”,让程序即使遇到问题也能够妥善处理,保证“健壮性”。
C++ 处理错误的标准方案是“异常”(exception)。虽然它已经在 Java、C#、Python 等语言中得到了广泛的认可和应用,但在 C++ 里却存在诸多争议。
你也可能在其他地方听到过一种说法:“现代 C++ 里应该使用异常。”但这之后呢?应该怎么去用异常呢?
所以,今天我就和你好好聊聊“异常那些事”,说一说为什么要有异常,该怎么用好异常,有哪些要注意的地方。
为什么要有异常?
很多人认为,C++ 里的“异常”非常可怕,一旦发生异常就是“了不得的大事”,这其实是因为没有理解异常的真正含义。
实际上,你可以按照它的字面意思,把它理解成“异于正常”,就是正常流程之外发生的一些特殊情况、严重错误。一旦遇到这样的错误,程序就会跳出正常流程,甚至很难继续执行下去。
归根到底,异常只是 C++ 为了处理错误而提出的一种解决方案,当然也不会是唯一的一种。
在 C++ 之前,处理异常的基本手段是“错误码”。函数执行后,需要检查返回值或者全局的 errno,看是否正常,如果出错了,就执行另外一段代码处理错误:
int n = read_data(fd, ...); // 读取数据
if (n == 0) {
... // 返回值不太对,适当处理
}
if (errno == EAGAIN) {
... // 适当处理错误
}
这种做法很直观,但也有一个问题,那就是正常的业务逻辑代码与错误处理代码混在了一起,看起来很乱,你的思维要在两个本来不相关的流程里来回跳转。而且,有的时候,错误处理的逻辑要比正常业务逻辑复杂、麻烦得多,看了半天,你可能都会忘了它当初到底要干什么了,容易引起新的错误。(你可以对比一下预处理代码与 C++ 代码混在一起的情景。)
错误码还有另一个更大的问题:它是可以被忽略的。也就是说,你完全可以不处理错误,“假装”程序运行正常,继续跑后面的代码,这就可能导致严重的安全隐患。(可能是无意的,因为你确实不知道发生了什么错误。)
“没有对比就没有伤害”,现在你就应该明白了,作为一种新的错误处理方式,异常就是针对错误码的缺陷而设