6.异常的一些知识

在C++之前,处理异常的基本手段是“错误码”,函数执行后需要见擦汗返回值或者全局的errno,如果出错了就执行另外一段代码处理错误

int n = read_data(fd, ...);//读取数据
if(n == 0){...}//
if(errno == EAGAIN){...}

异常是针对错误码的缺陷设计(和正常业务逻辑代码混在一起,可以被忽略),有三个特点:

  1. 异常的处理流程是完全独立的, throw抛出异常后,错误处理代码都集中在专门的catch里,这样就彻底分离了业务逻辑与错误逻辑。
  2. 异常是绝对不能被忽略的,必须被处理, catch捕获异常,会一直向上传播,直到找到一个能处理的catch块,如果实在没有,就会导致程序立即停止运行,提示发生了错误,
  3. 异常可以用在错误码无法使用的场合,。因为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”

总结:

  1. 异常是针对错误码的缺陷而设计的,异常不能被忽略。而且可以穿透调用栈,逐层传播到其他地方去处理
  2. 使用try-catch机制处理异常,能够分离正常流程与错误处理流程
  3. throw可以抛出任何类型作为异常,但最好使用标准库定义的exception嘞
  4. noexcept关键字标记函数不跑出异常,可以让编译器做更好优化

C++ 有finally吗?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值