在C语言中处理异常的方式为:
C++11处理异常方式:
try
{
//被保护的代码
}
catch (ExceptionName e1)
{
//catch块
}
catch (ExceptionName e2)
{
//catch块
}
catch (ExceptionName eN)
{
//catch块
}
异常的抛出和捕获的原则:
catch(...)
来进行捕捉,它可以捕捉任意类型。在函数调用链中异常栈展开的匹配原则:
void func1()
{
throw string("这是一个异常");
}
void func2()
{
func1();
}
void func3()
{
func2();
}
int main()
{
try
{
func3();
}
catch (const string& s)
{
cout << "错误描述:" << s << endl;
}
catch (...)
{
cout << "未知异常" << endl;
}
return 0;
}
当func1中的异常被抛出后:
上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。在实际中我们最后都要加一个catch(…)捕获任意类型的异常,否则当有异常没捕获时,程序就会直接终止。
有时候单个的catch可能不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链函数来处理,比如最外层可能需要拿到异常进行日志信息的记录,这时就需要通过重新抛出将异常传递给更上层的函数进行处理。
但如果直接让最外层捕获异常进行处理可能会引发一些问题。比如:
#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;
void func()
{
throw("我是一个func的异常");
}
void func2()
{
int* arr = new int[10];
func();
//TODO
delete[] arr;
}
int main()
{
try
{
func2();
}
catch(const string& s)
{
cout << s << endl;
}
catch (...)
{
cout << "未知异常" << endl;
}
return 0;
}
其中func2中通过new操作符申请了一块内存空间,并且在func2最后通过delete对该空间进行了释放,但由于func2中途调用的func1内部抛出了一个异常,这时会直接跳转到main函数中的catch块执行对应的异常处理程序,并且在处理完后继续沿着catch块往后执行。
这时就导致func2中申请的内存块没有得到释放,造成了内存泄露。这时可以在func2中先对func1抛出的异常进行捕获,捕获后先将申请到的内存释放再将异常重新抛出,这时就避免了内存泄露。比如:
#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;
void func()
{
throw("我是一个func的异常");
}
void func2()
{
int* arr = new int[10];
try
{
func();
}
catch (...)
{
delete[] arr;
cout << "释放成功" << endl;
throw;
}
//TODO
}
int main()
{
try
{
func2();
}
catch(const string& s)
{
cout << s << endl;
}
catch (...)
{
cout << "未知异常" << endl;
}
return 0;
}
func2中的new和delete之间可能还会抛出其他类型的异常,因此在fun2中最好以catch(…)的方式进行捕获,将申请到的内存delete后再通过throw重新抛出。
重新抛出异常对象时,throw后面可以不用指明要抛出的异常对象(正好也不知道以catch(…)的方式捕获到的具体是什么异常对象)。
将抛异常导致的安全问题叫做异常安全问题,对于异常安全问题下面给出几点建议:
为了让函数使用者知道某个函数可能抛出哪些类型的异常,C++标准规定:
throw(type1, type2, ...)
,列出这个函数可能抛掷的所有异常类型。throw()
或noexcept
(C++11),表示该函数不抛异常。比如:
//表示func函数可能会抛出A/B/C/D类型的异常
void func() throw(A, B, C, D);
//表示这个函数只会抛出bad_alloc的异常
void* operator new(std::size_t size) throw(std::bad_alloc);
//表示这个函数不会抛出异常
void* operator new(std::size_t size, void* ptr) throw();
实际中很多公司都会自定义自己的异常体系进行规范的异常管理。
class Exception
{
public:
Exception(int errid, const char* errmsg)
:_errid(errid)
, _errmsg(errmsg)
{}
int GetErrid() const
{
return _errid;
}
virtual string what() const
{
return _errmsg;
}
protected:
int _errid; //错误编号
string _errmsg; //错误描述
//...
};
其他模块如果要对这个异常类进行扩展,必须继承这个基础的异常类,可以在继承后的异常类中按需添加某些成员变量,或是对继承下来的虚函数what进行重写,使其能告知程序员更多的异常信息。比如:
class CacheException : public Exception
{
public:
CacheException(int errid, const char* errmsg)
:Exception(errid, errmsg)
{}
virtual string what() const
{
string msg = "CacheException: ";
msg += _errmsg;
return msg;
}
protected:
//...
};
class SqlException : public Exception
{
public:
SqlException(int errid, const char* errmsg, const char* sql)
:Exception(errid, errmsg)
, _sql(sql)
{}
virtual string what() const
{
string msg = "CacheException: ";
msg += _errmsg;
msg += "sql语句: ";
msg += _sql;
return msg;
}
protected:
string _sql; //导致异常的SQL语句
//...
};
说明一下:
C++标准库当中的异常也是一个基础体系,其中exception就是各个异常类的基类,我们可以在程序中使用这些标准的异常,它们之间的继承关系如下:
下表是对上面继承体系中出现的每个异常的说明:
异常的优点:
异常的缺点: