对于每位编程人员来说,程序的编译或运行错误是很常见的,今天我在这里粗略的总结一下C++中的异常
1、错误处理技术:
传统的错误处理办法:
(1)终止程序(如段错误等)
(2)返回错误码
(3)返回和法值,让程序出于某种非法的状态。(太坑了)
(4)调用一个预先设置的出现错误是调用的函数。——回调函数
2、异常的处理
当一个函数发现自己无法处理的错误是抛出异常,让函数的调用者直接或间接的处理这个问题
3、异常的抛出或捕获
(1)异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个处理代码。
(2)被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
(3)抛出异常后释放后会释放局部存储对象,所以被抛出的对象也还会还给系统,throw表达式会初始化一个抛出特殊的异常对象副本(匿名对象),异常对象由编译器管理,异常对象在传给对应的catch处理之后撤销。
4、栈展开
异常的时候,将暂停当前函数的执行,开始查找对应的匹配catch子句。
检查throw本身是否在try块内部,如果是在查找匹配的catch语句。
有匹配的,则处理。没有则退出当前函数栈,继续再调用函数的栈中进行查找。
重复上述过程。若到达main函数的栈,依旧没有匹配的,则终止程序。
这个沿着调用链查找匹配的catch子句的过程称为栈展开。
匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。
5、异常捕获的匹配规则
异常对象的类型与catch说明符的类型必须完全匹配。但以下情况例外:
(1)允许从非const对象到const对象的转换。
(2)允许从派生类型到基类类型的转换。
(3)将数组转换为指向数组类型的指针,将函数转换为指向函数类型的指针。
#include<iostream>
#include<string>
using namespace std;
class Excption
{
public:
Excption(int errId,const char* errMsg)
:_errId(errId)
,_errMsg(errMsg)
{}
void What() const
{
cout<<"errId:"<<_errId<<endl;
cout<<"errMsg:"<<_errMsg<<endl;
}
private:
int _errId;//错误码
string _errMsg;//错误消息
};
void Func1(bool isThrow)
{
if(isThrow)
{
throw Excption(1,"抛出Excption对象");
}
printf("Func1(%d)\n",isThrow);
}
void Func2(bool isThrowString,bool isThrowInt)
{
if(isThrowString)
{
throw string("抛出string对象");
}
if(isThrowInt)
{
throw 2;
}
printf("Func2(%d,%d)\n",isThrowString,isThrowInt);
}
void Func()
{
try
{
Func1(false);
Func2(true,true);
}
catch(const string& errMsg)
{
cout<<"Ctach string Object:"<<errMsg<<endl;
}
catch(int errId)
{
cout<<"Catch int Object:"<<errId<<endl;
}
catch(const Excption& e)
{
e.What();
}
catch(...)
{
cout<<"未知异常"<<endl;
}
printf("Func()\n");
}
int main()
{
Func();
return 0;
}
异常的重新抛出
有可能单个的catch不能让=完全处理一个异常,在运行一些校正处理以后,希望再交给更外层的调用链函数来处理,catch则可以通过重新抛出异常传递给更上层的函数进行处理。
class Excption
{
public:
Excption(int errId=0,const char* errMsg="")
:_errId(errId)
,_errMsg(errMsg)
{}
void What() const
{
cout<<"errId:"<<_errId<<endl;
cout<<"errMsg:"<<_errMsg<<endl;
}
private:
int _errId;//错误码
string _errMsg;//错误消息
};
void Func1()
{
throw string("Throw Func1 string");
}
void Func2()
{
try
{
Func1();
}
catch(string& errMsg)
{
cout<<errMsg<<endl;
}
}
void Func3()
{
try
{
Func2();
}
catch(Excption& e
{
e.What();
}
}
int main()
{
Func3();
return 0;
}
6、异常与构造函数&析构函数
(1)构造函数完成对象的构造和初始化,需要保证不要在构造函数中抛出异常,否则可能导致对象不完整或没有初始化。
(2)析构函数主要完成资源的清理,需要保证不要在析构函数中抛出异常,否则可能导致资源泄漏
7、C++标准库定义的excetion类
class Exception1:public exception
{
public:
Exception1(int errId=0,const char* errMsg="")
:_errId(errId)
,_errMsg(errMsg)
{}
virtual const char* What() const
{
cout<<"errId:"<<_errId<<endl;
cout<<"errMsg:"<<_errMsg<<endl;
return _errMsg.c_str();
}
private:
int _errId;//错误码
string _errMsg;//错误消息
};
class Exception2:public exception
{
public:
Exception2(int errId=0,const char* errMsg="")
:_errId(errId)
,_errMsg(errMsg)
{}
virtual const char*what() const
{
cout<<"errId:"<<_errId<<endl;
cout<<"errMsg:"<<_errMsg<<endl;
return _errMsg.c_str();
}
private:
int _errId;//错误码
string _errMsg;//错误消息
};
void Func()
{
try
{
throw Exception1(1,"Exception1");
throw Exception2(2,"Exception2");
}
catch(exception& e)
{
e.what();
}
}
void Func1()
{
try
{
int *p=new int[0x7fffffff/4];
}
catch(std::bad_alloc&ba)
{
std::cout<<"bad_alloc caught:"<<ba.what()<<"\n";
}
catch(exception& e)
{
std::cout<<"bad_alloc caught:"<<e.what()<<"\n";
}
}
int main()
{
Func();
Func1();
return 0;
}
excption类是C++定义的一个标准异常的类,通常我们通过继承二线产品提exception类定义合适的异常类。
8、使用异常和使用返回错误码相比,优缺点是什么
(1)异常捕获后可清楚知道发生了什么错误,错误码只是整型,不能描述错误信息
(2)异常会打乱执行流,会影响调试分析代码
(3)异常存在安全问题,须尽量使用RAII配合
(4)许多C++第三方库(比如boost)使用异常,关闭异常将难以与之结合
(5)很多测试框架使用异常gtest/gmock--ut