自定义异常
实际中很多公司都会自定义自己的异常体系进行规范的异常管理
- 公司中的项目一般会进行模块划分 让不同的程序员或小组完成不同的模块 如果不对抛异常这件事进行规范那么负责最外层捕获异常的程序员就非常难受了 因为他需要捕获大家抛出的各种类型的异常对象
- 因此实际中都会定义一套继承的规范体系 先定义一个最基础的异常类 所有人抛出的异常对象都必须是继承于该异常类的派生类对象 因为异常语法规定可以用基类捕获抛出的派生类对象 因此最外层就只需捕获基类就行了
下面是基础的异常类代码展示
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; //错误描述 //... };
这里基类异常类中有两个保护成员分别是错误编号和错误描述
它还有一个虚函数waht()来发出错误描述
接着我们来看它的子类对象
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语句 //... };
这里要说明两点
- 因为在子类中要使用父类的成员变量 所以父类中的成员变量不能是私有的
- 基类Exception中的what成员函数最好定义为虚函数 方便子类对其进行重写 从而达到多态的效果
标准异常库
下面是C++中的标准异常库体系
下表是对上面继承体系中出现的每个异常的说明
异常的优缺点
优点:
- 异常对象定义好了 相比错误码的方式可以清晰准确的展示出错误的各种信息 甚至可以包含堆栈调用等信息 这样可以帮助更好的定位程序的bug。
- 返回错误码的传统方式有个很大的问题就是 在函数调用链中 深层的函数返回了错误 那么我们得层层返回错误码 最终最外层才能拿到错误。
- 很多的第三方库都会使用异常 比如boost、gtest、gmock等等常用的库,如果我们不用异常就不能很好的发挥这些库的作用
- 很多测试框架也都使用异常 因此使用异常能更好的使用单元测试等进行白盒的测试
- 部分函数使用异常更好处理 比如T& operator这样的函数 如果pos越界了只能使用异常或者终止程序处理 没办法通过返回值表示错误。
缺点
- 异常会导致程序的执行流乱跳 并且非常的混乱 这会导致我们跟踪调试以及分析程序时比较困难
- 异常会有一些性能的开销 当然在现代硬件速度很快的情况下 这个影响基本忽略不计
- C++没有垃圾回收机制 资源需要自己管理 有了异常非常容易导致内存泄露 死锁等异常安全问题 这个需要使用RAII来处理资源的管理问题 学习成本比较高
- C++标准库的异常体系定义得不够好 导致大家各自定义自己的异常体系 非常的混乱
- 异常尽量规范使用 否则后果不堪设想 随意抛异常 也会让外层捕获的用户苦不堪言
- 异常接口声明不是强制的 对于没有声明异常类型的函数 无法预知该函数是否会抛出异常
思维导图