在C++之前,处理异常的基本手段是“错误码”,函数执行后需要见擦汗返回值或者全局的errno,如果出错了就执行另外一段代码处理错误
int n = read_data(fd, ...);//读取数据
if(n == 0){...}//
if(errno == EAGAIN){...}
异常是针对错误码的缺陷设计(和正常业务逻辑代码混在一起,可以被忽略),有三个特点:
- 异常的处理流程是完全独立的,
throw
抛出异常后,错误处理代码都集中在专门的catch
里,这样就彻底分离了业务逻辑与错误逻辑。 - 异常是绝对不能被忽略的,必须被处理,
catch
捕获异常,会一直向上传播,直到找到一个能处理的catch块,如果实在没有,就会导致程序立即停止运行,提示发生了错误, - 异常可以用在错误码无法使用的场合,。因为C++比C多了构造/析构函数、操作符重载等新特性,有的函数根本没有返回值,或者返回值无法表示错误,而全局的
errno
显然不太ok
C++ 引入异常处理机制,其基本思想是:函数 A 在执行过程中发现异常时可以不加处理,而只是“拋出一个异常”给 A 的调用者,假定为函数 B。拋出异常而不加处理会导致函数 A 立即中止,在这种情况下,函数 B 可以选择捕获 A 拋出的异常进行处理,也可以选择置之不理。如果置之不理,这个异常就会被拋给 B 的调用者,以此类推。如果一层层的函数都不处理异常,异常最终会被拋给最外层的 main 函数。main 函数应该处理异常。如果main函数也不处理异常,那么程序就会立即异常地中止。
异常的用法和使用方法
用try
把可能发生异常的代码包起来,然后编写catch
块捕获异常并处理。
try
{
int n = read_data(fd, ...);
}
catch(...)
{
...
}
语法示例:
try语句块判断是否有异常;
catch语句块捕捉异常,并进行处理;
throw抛出异常;
#include <stdlib.h>
#include "iostream"
using namespace std;
double fuc(double x, double y) //定义函数
{
if(y==0)
{
throw y; //除数为0,抛出异常
}
return x/y; //否则返回两个数的商
}
int main(int argc, _TCHAR* argv[])
{
double res;
try //定义异常
{
res=fuc(2,3);
cout<<"The result of x/y is : "<<res<<endl;
res=fuc(4,0); //出现异常
}
catch(double) //捕获并处理异常
{
cerr<<"error of dividing zero.\n";
exit(1); //异常退出程序
}
return 0;
}
C++对于异常的定义非常宽松,任何类型都可以用throw
抛出,catch也能接住。但C++已经为处理异常设计了一个配套的异常类型体系,定义在标准库<stdexcept>
头文件里
自己写的时候,可以选择前两层的某个类型作为基类,再派生自己的异常类,比如
可以从runtime_error
派生出自己的异常类
class my_exception : public std::runtime_error
{
public:
using this_type = my_exception;//给自己起个别名
using super_type = std::runtime_error;//给父类起别名
public:
my_exception(const char* msg):super_type(msg){}//构造函数
my_exception() = default;//默认构造函数
~my_exception() = default;
private:
int code = 0;
};
使用catch
捕获异常的时候要注意,C++允许编写多个catch块捕获不同的异常,再分别处理;但是异常只能按照catch块在代码里的顺序依次匹配,而不会去找最佳匹配。(??所以最好只用一个catch块??)
try
{
raise("error occured");//将throw封装成一个函数更安全 :抛出异常
}
catch(const exception& e)//const& 捕获异常,可以用基类!!
{
cout<<e.what()<<endl;
}
void raise(const char *msg)
{
throw my_exception(msg);
}
异常的使用
异常的抛出和处理需要特别的栈展开stack unwind
操作,如果异常出现的位置很深,但又没有被及时处理,或者频繁地抛出异常,就会对运行性能产生很大的影响。这个时候,程序全忙着去处理异常,正常逻辑反而会被搁置。
noexcept
是C++提出的一个新的编译阶段的指令,既可以享受异常的好处,又不用承担异常的成本。noexcept
专门用来修饰函数,告诉编译器,这个函数不会抛出异常,编译器看到noexcept
就可以对函数做优化,不去加那些栈展开的额外代码,消除异常处理的成本。
“栈展开(stackunwinding)是指,如果在一个函数内部抛出异常,程序暂停当前函数的执行过程并立即开始查找(look up)最邻近的与异常匹配的 catch 子句,若此异常并未在该函数内部被捕捉,就将导致该函数的运行在抛出异常处结束,所有已经分配在栈上的局部变量都要被释放。” 可以参考
和const一样,noexcept放在函数后面,但是noexcept只是做了一个“不可靠承诺”,不是“强保证”,编译器无法彻底检查它的行为,标记为noexcept的函数也可能抛出异常
void func_maybe_noexcept() noexcept
{
throw "aiya maya";//还是抛出了
}
所以承诺的意思是“对外承诺不抛出异常,也不想处理异常,如果真的有异常发生,就直接崩溃crash、 core_dump”
总结:
- 异常是针对错误码的缺陷而设计的,异常不能被忽略。而且可以穿透调用栈,逐层传播到其他地方去处理
- 使用try-catch机制处理异常,能够分离正常流程与错误处理流程
- throw可以抛出任何类型作为异常,但最好使用标准库定义的exception嘞
noexcept
关键字标记函数不跑出异常,可以让编译器做更好优化
C++ 有finally吗?