C++异常
- 一.C++为什么发明异常?
- 二.异常的基本语法
- 三.异常的抛出和捕获
- 四.自定义异常体系
- 五.异常的重新抛出
-
- 1.为什么要将异常重新抛出?
- [2.异常的缺陷 -> 引入智能指针的原因](#2.异常的缺陷 -> 引入智能指针的原因)
- 六.异常规范
- 七.异常安全
- 八.C++标准库的异常体系
- 九.异常的优缺点
异常是一种处理错误的方式,
当一个函数遇到自己无法处理的错误时就可以抛出异常,让函数的直接调用者和间接调用者来处理这个问题
一.C++为什么发明异常?
下面我们演示一下
学习异常之后,这么难看的代码我们就再也不用写了
我们来演示一下
不过异常好是好,但是也有自己的问题和不足
我们在这篇博客当中就能看到异常的问题与不足
以及C++为了弥补异常的不足所作的努力
二.异常的基本语法
下面我们来简单的用一下
不过现在有一个问题,异常跳出栈帧的时候,栈帧会正常销毁吗?栈帧里面的数据会正常释放吗?
栈帧会正常销毁的,数据会正常释放的
我们来验证一下
我们知道,局部自定义类型对象出了作用域后在销毁之前要先调用析构函数来清理资源
浅浅的了解了异常的使用之后,下面我们来看一下细节点
三.异常的抛出和捕获
1.异常的抛出和匹配原则
2.函数调用链中异常栈的展开匹配原则
为什么要有catch(...)呢?
是为了作一层保险,防止有人乱抛异常
四.自定义异常体系
为了防止有人乱抛异常,公司当中都会自定义自己的异常体系来对异常作规范管理
同时为了便于后续处理异常,经常会在main函数或者程序的主逻辑函数当中将所有的异常写入到日志当中
因此大多数情况下异常是交由main/程序的主逻辑函数来catch的
下面我们简单看一下一个异常体系
cpp
#include <Windows.h>//Sleep函数的头文件
//服务器开发中通常使用的异常继承体系
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='张三'");
}
}
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)
{
Sleep(1000);
try
{
HttpServer();
}
catch (const Exception& e)//捕获基类对象就可以了
{
//多态
cout << e.what() << endl;
}
catch (...)
{
cout << "Unkown Exception" << endl;
}
}
return 0;
}
main函数中只需要捕获基类对象就可以捕获到各种各样的异常类型
其中:
基类Execption中的what成员函数最好定义为虚函数,让子类进行重写,形成多态
五.异常的重新抛出
1.为什么要将异常重新抛出?
有些情况下,由于发生了异常,导致有些该释放的资源还没有来得及释放,因而产生内存泄漏
比如:
此时就需要在func函数当中进行catch了,catch之后为了要让main函数记录日志,所以要将异常重新抛出
此时func捕获异常不是为了处理异常,而是为了清理资源,防止发生异常导致的内存泄漏
不过我func捕获异常只是为了清理资源而已啊,你抛出的异常是什么类型跟我有什么关系,反正我拿到之后,也得抛给main函数,那么我不就可以完全不关心你这个异常的类型了吗?
因此,在实践当中,更推荐这种写法
cpp
catch (...)
{
delete p;
throw;//抛出...的异常时,直接使用throw即可,因为...不是这个异常的名字
}
实际当中也不一定就是delete和new造成的内存泄漏
也有可能是僵尸进程或者fd或者管道,套接字等等
文件打开没有关闭(尽管进程结束之后文件会自动关闭,但是给该进程分配的fd无法被重新利用,而每个进程的fd又是有限的,因此也会造成内存泄漏)
因此内存泄漏并不只是单纯指堆区资源忘记释放,也有可能是OS所分配的资源没有被及时释放,
其实说白了就是你占着资源,而且这个资源你再也不用了,OS也没法让别人用
这就是内存泄漏
2.异常的缺陷 -> 引入智能指针的原因
刚才对于异常的捕获和重新抛出看似完全解决了内存泄漏,但是对于这么一个场景呢?
是不是很麻烦,而且代码很难看
因此C++11引入了智能指针
(当然C++98当中有一个auto_ptr,也是针对于这个问题而设计的,不过被很多人所吐槽,因此C++11引入了unique_ptr,shared_ptr,(而且因为shared_ptr有一些问题,又引入了weak_ptr来解决shared_ptr的问题))
关于智能指针,我们后面会详细介绍的
这里大家知道异常对于这种问题的处理很难受,因此才有了智能指针的出现
六.异常规范
异常规范说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些,但是异常规范只是建议,编译器因为要兼容C语言,所以对异常规范的不合理使用并没有任何报错,最多只是报warning
cpp
// 这里表示这个函数会抛出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;
七.异常安全
1.构造函数中不要抛异常
2.noexcept与运行时抛异常
对于析构函数抛异常的处理C++98和C++11的处理方式不同
在VS2019下(对于异常,支持了C++11的新语法)
对析构函数用noexcept进行修饰
编译时不会报错,只会报个warning,但是运行时只要这个被noexcept修饰的函数被调用了,那么就会报错
我们在Linux环境下用g++来编译一下看一看
然后我们切成c++98标准来编译
不过你要是在析构当中补了,当我没说
总而言之,一句话就是,你可以在noexcept修饰的函数当中抛异常,但是如果函数运行时异常出现了,而且你没有在当前函数中捕获,那么就会直接报错
而编译时只会报警,不会报错,因为编译时不知道你这个异常会不会出现啊(如果有一天编译器能根据程序来推出它的具体运行逻辑,并且编写代码的时候,我们也差不多失业了...)
八.C++标准库的异常体系
C++ 提供了一系列标准的异常,定义在exception中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的
实际中我们可以可以去继承exception类实现自己的异常类。
但是实际中很多公司像上面一样自己定义一套异常继承体系。
因为C++标准库设计的不够好用
九.异常的优缺点
以上就是C++异常的全部内容,希望能对大家有所帮助!