C++异常,c++处理异常的机制
为什么C语言中引入异常的错误处理通常是通过返回值或者使用全局变量来完成?有两个问题。第一,如果返回值正好是我们需要的数据,那么返回数据和错误数据的容错差就不高。
二是全局变量,容易导致多线程竞争。而且发生错误时,上级功能要处理并上报给上级,导致错误处理码过多,传输效率低。
因此,C引入了面向对象的异常处理机制。
在C中,异常处理和具体的逻辑处理不在同一个函数中,这样下层逻辑可以专注于函数的实现,具体的错误处理将由上层的业务逻辑来处理。
如何异常使用?使用关键字throw抛出异常,并尝试使用catch代码块来捕获异常。这里有一个简单的例子:
使用命名空间std
void func(){
//引发异常
int a=100
int b=0;
尝试{
//可能会引发异常
if(b==0){
抛出字符串(除数为0 );
}否则{
int c=a/b;
}
}catch(int e) {
}
}
int main() {
尝试{
//捕捉可能的异常
func();
}catch(字符串e){
//处理异常
STD:cout e STD:endl;
}
返回0;
}如上例所示,当func函数抛出异常时,如果在throw后面的try…catch…块中找不到与异常对象相匹配的catch语句,异常将由外层的try…catch…块处理;如果当前函数中的所有try…catch…块都不能匹配异常,递归返回到调用堆栈的上层来处理异常。如果在退回到主函数main()之前无法处理异常,请调用系统函数terminate()来终止程序。
抛出异常时,程序暂停当前函数的执行,并立即开始寻找与异常类型匹配的catch代码块。如果找到匹配的catch,它将使用catch来处理异常。如果在此步骤中没有找到匹配的catch,并且try语句嵌套在其他try块中,它将继续检查与外部try匹配的catch代码块。
如果还是找不到匹配的catch代码块,退出当前函数,继续在调用当前函数的外层函数中搜索。
最后如果找不到catch块,异常会被系统处理,系统一般默认的处理方式是调用系统函数terminate()终止程序。
例如,在上面的例子中,func函数抛出了一个string类型的异常,但是在func函数中只捕捉到了int类型的异常,所以栈展开后在main函数中找到了匹配的catch块。
抛出异常时,调用链上的块会依次退出,直到找到匹配异常的处理代码,这就是堆栈展开的过程。
有时,单个catch语句不能完全处理异常。在执行了一些纠正操作之后,当前catch可能会被移交给调用链中更高级别的函数来处理异常。这时,可以使用不带参数的关键字throw再次抛出异常。
注意,这里的re-throw仍然是一个throw语句,但是它不包含任何表达式。
使用命名空间std
void func(){
//引发异常
int a=100
int b=0;
尝试{
//可能会引发异常
if(b==0){
抛出字符串(除数为0 );
}否则{
int c=a/b;
}
}catch(字符串e) {
“Std:cout”捕获到异常并引发了“STD:endl;
E=e ,内部部分处理;
//重新引发异常
扔;
}
}
int main() {
尝试{
//捕捉可能的异常
func();
}catch(字符串e){
//处理异常
STD:cout e STD:endl;
}
返回0;
}使用指针抛出异常,然后又抛出异常怎么办?在哪个步骤中删除这个指针?这是个纠结的问题?
异常捕捉类型就像函数一样,可以通过值、指针和引用来传递。在catch代码块中,我们还可以捕捉值、异常捕捉指针和异常捕捉引用。既然选择这么多,抓什么才是最好的解决方案?
对于好的东西,当然是越多越好,但是对于不正常的东西,就是洪水猛兽。
《More Effective C++》中的第13条:
通过引用捕获异常
也就是说,大师建议我们通过异常捕获和引用的方式传递异常信息。你为什么这么建议?异常捕获值和异常捕获指针的缺点是什么?更多详情,童鞋们可以看书《More Effective C++》。
在这里,我简单说明一下原因:
1.c是基于面向对象思想的语言,所以抛出的异常必然会有继承关系,而继承一般会涉及多态,触发多态所需的动态绑定需要指针或引用来实现,所以异常值传递的意义基本没有了。
2.异常捕捉指针,那么谁将设法释放这个指针?这个指针什么时候发布?所以捕捉异常指针会带来内存管理的问题。
3.似乎只剩下异常捕捉引用这一个选项了,以上两个问题都不存在。如果异常需要再次抛出,引用所做的修改也可以保留下来继续异常传输,所以异常捕捉引用就是自然选择的儿子。
由于建议使用异常捕捉引用来处理异常,所以当异常发生时抛出的对象通常是局部变量。在平时写程序的过程中,我们总是被强调不能返回一个对局部变量的引用。这里的变态引用不是矛盾吗?
这里需要注意的一点是,当一个对象作为异常抛出时,它总是会被复制。即使抛出的对象没有崩溃的危险,复制行为还是会发生,所以如果有自定义类型,就考虑自定义类型的复制。
让我们用下面的代码来验证一下:
使用命名空间std
A级
公共:
A(int a,string text):年龄(a),姓名(text){
Std:cout“自定义构造函数”endl
}
A(常数A a){
Std:cout“复制构造”endl
}
~A(){
Std:cout“析构函数”endl
}
公共:
int age
std:字符串名称;
};
A createA(){
A a(10,小明);
返回a;
}
void testException(){
A a(10,小明);
扔一个;
}
int main() {
a a=createA();
cout age: a . age endl;//由于返回的临时对象的引用已经狂喜,输出年龄错误
cout - endl;
尝试{
test exception();
}catch (A a){
“Cout”捕获了一个异常:“a.age endl
}
返回0;
}输出:
自定义构造函数
破坏者
复制结构
年龄:83738720
-
自定义构造函数
复制结构
破坏者
捕获异常:0
破坏者
析构函数在上面的程序中,我们发现普通函数中返回临时对象的引用的复制函数和析构函数的执行顺序与异常中使用引用捕捉它们的执行顺序不同。
所以通过引用捕获异常和不返回函数中临时对象的引用并不冲突。
所以又有一个问题,难道不是通过引用传递参数就可以减少复制吗?为什么在异常中使用引用时要复制?
这里我们需要记住一个特殊的情况:
特别是在C中,需要说明的是,与普通函数的调用不同,当一个对象作为异常抛出时,它总是会被复制的。但是,也有一个例外,当catch中捕获到异常引用时,再次抛出时不会复制。
在新的C 11标准中,我们可以通过提供Noexcept来指定函数不会抛出异常。
noexcept说明符接受可选参数,该参数必须可转换为bool类型:如果参数为true,则函数不会引发异常;如果参数为false,该函数可能会引发异常:
void testPrint() noexcept(true){
//不会引发任何异常
}
void test print 2()no except(false){
//可能会引发异常
}
void test print()no exception {
//没有参数,这意味着不会引发任何异常
}用noexcept声明一个函数,告诉编译器这个函数不会抛出异常,编译器可以根据声明优化代码。但是noexcept只是告诉编译器不会抛出异常,但是函数不一定真的抛出异常。
当我们在没有声明异常的函数中抛出异常时,程序会调用std:terminate来结束程序的生命周期。
请参考圣经《More Effective C++》和《Effective C++》,了解更多关于异常处理的注意事项。
关注我,共同进步,生活不止编码!
对于来自,
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。