目录
一、C语言的异常处理方式
C语言对于异常处理方式通常为直接终止程序并返回错误码(大部分函数接口都会将错误码放在errno中以示错误原因)
直接终止程序用户难以接受,返回错误码又需要程序员自己查找对应的错误。
错误码及其对应的错误原因如下:
二、C++异常处理基本概念
C++中引入了异常,异常是一种错误处理方式。当一个函数出现自己无法处理的错误时,就可以抛出异常,让函数的调用者处理该异常。
- throw:函数通过throw关键字抛出异常
- try:标记可能会抛出异常的代码块,如果抛出异常后程序会立即跳转到与try标记块相关联的catch子句处
- catch:捕获并处理异常,每个try块之后可能会有多个catch子句处理不同的异常,每一个catch子句中都声明了异常类型并定义了一个异常对象
三、异常处理的使用
1.异常抛出和捕获的匹配原则
C++的异常抛出其实是抛出一个异常对象,该异常对象的类型决定了与哪个catch异常处理子句匹配。抛出异常对象后,会调用异常对象的拷贝构造生成一个异常对象的拷贝,因为抛出的异常对象是函数生成的局部对象,只存在于当前函数栈帧中,而异常处理可能会涉及多个函数栈帧。
匹配原则:
函数抛出异常后,首先会检查throw是否在try块内部:
- 如果在try块内部,选择最近的且类型匹配catch子句。如果没有类型匹配的catch子句,则退出当前函数栈帧,在调用函数的栈帧中查找匹配的catch子句,直到查找到匹配的catch子句。如果查找到main函数的栈帧依旧没有找到匹配的catch子句,则程序终止。
- 如果不在try块内部,则退出当前函数栈帧,跳到调用函数的栈帧中看是否在try块内部,直到跳转到try块背部,重复第一点操作
double Div(double x, double y)
{
if (y == 0)
throw "Dividing by 0";//抛出异常
else
return x / y;
}
void Func()
{
int a, b;
std::cin >> a >> b;
std::cout << Div(a, b) << std::endl;
}
int main()
{
try {
Func();
}
catch (const char* error_msg)
{
std::cout << error_msg << std::endl;
}
catch (int error_msg)
{
std::cout << error_msg << std::endl;
}
catch (...)
{
std::cout << "unknown exception" << std::endl;
}
return 0;
}
2.异常的重新抛出
有时候单个的catch子句并不能完全处理一个异常,catch子句在对异常进行一些校正处理后,可以将该异常重新抛出给更上层的调用函数来处理。
double Div(double x, double y)
{
if (y == 0)
throw "Dividing by 0";//抛出异常
else
return x / y;
}
void Func()
{
int* arr = new int[10];
try {
int a, b;
std::cin >> a >> b;
std::cout << Div(a, b) << std::endl;
}
catch (...)
{
std::cout << "delete []" << arr << std::endl;
delete[] arr;
throw;//重新抛出异常
}
std::cout << "delete []" << arr << std::endl;
delete[] arr;
}
int main()
{
try {
Func();
}
catch (const char* error_msg)
{
std::cout << error_msg << std::endl;
}
catch (int error_msg)
{
std::cout << error_msg << std::endl;
}
catch (...)
{
std::cout << "unknown exception" << std::endl;
}
return 0;
}
3.不建议抛出异常的情况
随意抛出异常可能会导致程序错误,为了异常安全,下面几种情况建议不要抛出异常
- 在构造函数中不要抛出异常,构造函数负责对象的构造和初始化,如果在构造函数中抛出异常可能会导致对象不完整或者没有完全初始化
- 在析构函数中不要抛出异常,析构函数负责完成资源的清理,如果在析构函数中抛出异常可能会导致资源泄露问题
- 在new和delete中不要抛出异常,可能会导致资源泄露
- 在lock和unlock之间不要抛出异常,可能会导致死锁
4.抛出异常规范
- 为了让函数使用者知道函数可能抛出哪些异常,在函数声明后接上throw(A,B,C,D),A,B,C,D表示抛出异常的类型
- 有些函数不抛出异常,在函数声明后接上throw()
- C++11中表示函数不需要抛出异常,在函数声明后接上noexcept
// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
void fun() throw(A,B,C,D);
// 这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);
// 这里表示这个函数不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw();
// C++11 中新增的noexcept,表示不会抛异常
thread() noexcept;
thread (thread&& x) noexcept;
四、抛出派生类对象,使用基类捕获
实际使用中很多公司都会自定义自己的异常体系进行规范的异常管理,因为一个项目中如果大家
随意抛异常,那么外层的调用者基本就没办法玩了,所以实际中都会定义一套继承的规范体系。
这样大家抛出的都是继承的派生类对象,捕获一个基类就可以了。
// 服务器开发中通常使用的异常继承体系
class Exception
{
public:
Exception(const string& errmsg, int id)
:_errmsg(errmsg)
, _id(id)
{}
virtual string what() const
{
return _errmsg;
}
protected:
string _errmsg;
int _id;
};
class SqlException : public Exception
{
public:
SqlException(const string& errmsg, int id, const string& sql)
:Exception(errmsg, id)
, _sql(sql)
{}
virtual string what() const
{
string str = "SqlException:";
str += _errmsg;
str += "->";
str += _sql;
return str;
}
private:
const string _sql;
};
class CacheException : public Exception
{
public:
CacheException(const string& errmsg, int id)
:Exception(errmsg, id)
{}
virtual string what() const
{
string str = "CacheException:";
str += _errmsg;
return str;
}
};
class HttpServerException : public Exception
{
public:
HttpServerException(const string& errmsg, int id, const string& type)
:Exception(errmsg, id)
, _type(type)
{}
virtual string what() const
{
string str = "HttpServerException:";
str += _type;
str += ":";
str += _errmsg; return str;
}
private:
const string _type;
};
void SQLMgr()
{
srand(time(0));
if (rand() % 7 == 0)
{
throw SqlException("权限不足", 100, "select * from name = '张三'");
}
//throw "xxxxxx";
}
void CacheMgr()
{
srand(time(0));
if (rand() % 5 == 0)
{
throw CacheException("权限不足", 100);
}
else if (rand() % 6 == 0)
{
throw CacheException("数据不存在", 101);
}
SQLMgr();
}
void HttpServer()
{
// ...
srand(time(0));
if (rand() % 3 == 0)
{
throw HttpServerException("请求资源不存在", 100, "get");
}
else if (rand() % 4 == 0)
{
throw HttpServerException("权限不足", 101, "post");
}
CacheMgr();
}
int main()
{
while (1)
{
this_thread::sleep_for(chrono::seconds(1));
try {
HttpServer();
}
catch (const Exception& e) // 这里捕获父类对象就可以
{
// 多态
cout << e.what() << endl;
}
catch (...)
{
cout << "Unkown Exception" << endl;
}
}
return 0;
}