目录
一、C语言传统的处理错误的方式
二、 C++异常概念
三、异常的使用
1.异常的抛出和捕获
异常的抛出和匹配原则
在函数调用链中异常栈展开匹配原则
2.异常的重新抛出
3.异常安全
4.异常规范
四、自定义异常体系
五、C++标准库的异常体系
六、异常的优缺点
#include
using namespace std;
int Division(int x, int y)
{
if (y == 0)
{
//抛出异常
throw "除数不能为0!";
}
else
{
return x / y;
}
}
int main()
{
//try中的语句收到保护
try
{
int x, y;
cin >> x >> y;
Division(x, y);
}
//catch 来捕捉try中保护的语句抛出的异常
catch(const char* str)
{
cout << str << endl;
}
return 0;
}
我们可以看到,当我们抛出了异常之后,就会被捕捉到,我们就可以打印出异常信息。
int Division(int x, int y)
{
if (y == 0)
{
//抛出异常
throw "除数不能为0!";
}
else
{
return x / y;
}
}
int main()
{
//try中的语句收到保护
try
{
int x, y;
cin >> x >> y;
Division(x, y);
}
//catch 来捕捉try中保护的语句抛出的异常
catch(const char* str)
{
cout << str << endl;
}
catch (int ret)
{
cout << ret << endl;
}
return 0;
}
我们返回的是字符串,匹配到的catch也就是上面的const char* 类型的。
int Division(int x, int y)
{
if (y == 0)
{
//抛出异常
throw "除数不能为0!";
}
else
{
return x / y;
}
}
void Func(int x, int y)
{
try
{
Division(x, y);
}
catch (const char* str)
{
cout << "Func中捕捉:" << str << endl;
}
}
int main()
{
//try中的语句收到保护
try
{
int x, y;
cin >> x >> y;
Func(x, y);
}
//catch 来捕捉try中保护的语句抛出的异常
catch(const char* str)
{
cout << "main中捕捉:" << str << endl;
}
catch (int ret)
{
cout << ret << endl;
}
return 0;
}
如果我们在Func中开辟了空间,那么还没到结尾就抛异常退出函数了,空间没有得到有效的释放,就会造成内存泄漏。
为了解决这个问题,我们需要在catch中再释放空间,如果有多个catch,我们就要写多份重复的代码。
int Division(int x, int y)
{
if (y == 0)
{
//抛出异常
throw "除数不能为0!";
}
else
{
return x / y;
}
}
void Func(int x, int y)
{
int* pi = new int[10];
try
{
Division(x, y);
}
catch (const char* str)
{
cout << "Func中捕捉:" << str << endl;
delete[] pi;
throw str;
}
catch(int errid)
{
cout << "Func中捕捉:" << errid << endl;
delete[] pi;
throw errid;
}
}
智能指针能够帮助我们自动释放空间,我们后面的文章中会介绍智能指针的用法。
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
using namespace std;
class Exception
{
public:
Exception(const string& errmg, int id)
:_errmg(errmg)
,_id(id)
{}
virtual string what() const
{
return _errmg;
}
protected:
string _errmg;
int _id;
};
//缓存访问子类
class CacheException : public Exception
{
public:
CacheException(const string& errmsg, int id)
:Exception(errmsg, id)
{}
virtual string what() const
{
string str = "CacheException:";
str += _errmg;
return str;
}
};
class SqlException : public Exception
{
public:
SqlException(const string& errmg, int id, const string& sql)
:Exception(errmg, id)
, _sql(sql)
{}
virtual string what() const
{
string str = "SqlException";
str += _sql;
str += ":";
str += _errmg;
return str;
}
private:
const string _sql;
};
class HttpServerException : public Exception
{
public:
HttpServerException(const string& errmg, int id, const string& http)
:Exception(errmg, id)
, _http(http)
{}
virtual string what() const
{
string str = "HttpServerException";
str += _http;
str += ":";
str += _errmg;
return str;
}
private:
const string _http;
};
//数据库查询
void SQLMgr()
{
srand(time(0));
if (rand() % 4 == 0)
{
throw SqlException("权限不足", 100, "select * from name = '张三'");
}
}
//缓存访问
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() % 7 == 0)
{
throw SqlException("资源不存在", 100, "get");
}
else if (rand() % 8 == 0)
{
throw SqlException("权限不足", 101, "post");
}
CacheMgr();
}
int main()
{
while (1)
{
try
{
HttpServer();
}
catch (const Exception& ep) //这里只需要捕获父类即可
{
cout << ep.what() << endl;
}
Sleep(500);
}
return 0;
}
优点:
1. 异常对象定义好了, 相比错误码的方式可以清晰准确的展示出错误的各种信息 ,甚至可以包含堆栈调用的信息,这样可以帮助更好的定位程序的 bug 。2. 返回错误码的传统方式有个很大的问题就是,在函数调用链中,深层的函数返回了错误,那么我们得层层返回错误,最外层才能拿到错误,具体看下面的详细解释。3. 很多的第三方库都包含异常,比如 boost 、 gtest 、 gmock 等等常用的库,那么我们使用它们也需要使用异常。4. 部分函数使用异常更好处理,比如构造函数没有返回值,不方便使用错误码方式处理。比如T& operator这样的函数,如果 pos 越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误。
缺点:
1. 异常会导致程序的执行流乱跳,并且非常的混乱,并且是运行时出错抛异常就会乱跳。这会导致我们跟踪调试时以及分析程序时,比较困难。2. 异常会有一些性能的开销。当然在现代硬件速度很快的情况下,这个影响基本忽略不计。3. C++ 没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常安全问题。这个需要使用RAII 来处理资源的管理问题。学习成本较高。4. C++ 标准库的异常体系定义得不好,导致大家各自定义各自的异常体系,非常的混乱。5. 异常尽量规范使用,否则后果不堪设想,随意抛异常,外层捕获的用户苦不堪言。所以异常规范有两点:一、抛出异常类型都继承自一个基类。二、函数是否抛异常、抛什么异常,都使用 func() throw(); 的方式规范化。
总结: