🌈欢迎来到C++专栏~~异常
- (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是Scort
- 目前状态:大三非科班啃C++中
- 🌍博客主页:张小姐的猫~江湖背景
- 快上车🚘,握好方向盘跟我有一起打天下嘞!
- 送给自己的一句鸡汤🤔:
- 🔥真正的大师永远怀着一颗学徒的心
- 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏
- 🎉🎉欢迎持续关注!


传统的错误处理机制:
实际中C语言基本都是使用返回错误码的方式处理错误,部分情况下使用终止程序处理非常严重的错误
异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或间接的调用者处理这个错误
throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的catch: 在您想要处理问题的地方,通过异常处理程序捕获异常 catch 关键字用于捕获异常,可以有多个catch进行捕获try: try 块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch 块如果有一个块抛出一个异常,捕获异常的方法会使用 try 和 catch 关键字。try 块中放置可能抛出异常的代码,try 块中的代码被称为保护代码。使用 try/catch 语句的语法如下所示:
try
{//被保护的代码
}
catch (ExceptionName e1)
{//catch块
}
catch (ExceptionName e2)
{//catch块
}
catch (ExceptionName eN)
{//catch块
}
catch(...)可以 捕获任意类型的异常,问题是不知道异常错误是什么在函数调用链中异常栈展开的匹配原则:

下面代码中,
double Division(int a, int b)
{// 当b == 0时抛出异常if (b == 0)throw "Division by zero condition!";elsereturn ((double)a / (double)b);
}
void Func()
{try{int len, time;cin >> len >> time;cout << Division(len, time) << endl;}catch (const char* errmsg){cout << errmsg << endl;}cout << "Func() end" << endl;
}
int main()
{try {Func();}catch (int errid){cout << errid << endl;}catch (const char* errmsg){cout << errmsg << endl;}return 0;
}
当Division抛异常后:
上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。在实际中我们最后都要加一个catch(...)捕获任意类型的异常,否则当有异常没捕获时,程序就会直接终止
catch (...)//捕获任意类型的异常 -- 防止出现未捕获异常时,程序崩溃
{cout << "未知异常" << endl;
}
防止出现:

有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数进行处理
此处如果Division抛异常了,后面的delete语句不会执行,就会导致内存泄漏
void Func()
{// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放int* array = new int[10];int len, time;cin >> len >> time;cout << Division(len, time) << endl;cout << "delete []" << array << endl;delete[] array;
}
所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再重新抛出去,这时就避免了内存泄露
void Func()
{// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。// 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再重新抛出去。int* array = new int[10];int len, time;cin >> len >> time;try{cout << Division(len, time) << endl;}catch (...){cout << "delete []" << array << endl;delete[] array;throw; //捕获什么,抛出什么}cout << "delete []" << array << endl;delete[] array;
}
func()中的new和delete之间可能还会抛出其他类型的异常,因此在fun2中最好以catch(…)的方式进行捕获,将申请到的内存delete后再通过throw重新抛出对于异常安全问题下面给出几点建议:
为了让函数使用者知道某个函数可能抛出哪些类型的异常,C++标准规定:
throw(type1, type2, ...),列出这个函数可能抛掷的所有异常类型throw()或noexcept(C++11),表示该函数不抛异常//C++98
// 这里表示这个函数会抛出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;}int getid(){return _id;}
protected:string _errmsg; //错误信息int _id; //错误码
};
其他模块如果要对这个异常类进行扩展,必须继承这个基础的异常类,可以在继承后的异常类中按需添加某些成员变量,或是对继承下来的虚函数what进行重写,使其能告知程序员更多的异常信息
//缓存
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;
};
要注意:
what成员函数最好定义为虚函数,方便子类对其进行重写,从而达到多态的效果C++标准库当中的异常也是一个基础体系,其中exception就是各个异常类的基类,我们可以在程序中使用这些标准的异常,它们之间的继承关系如下:


说明:
C++异常的优点:
gmock等等常用的库,如果我们不用异常就不能很好的发挥这些库的作用异常的缺点:
异常会导致程序的执行流乱跳,并且非常的混乱,这会导致我们跟踪调试以及分析程序时比较困难。
异常会有一些性能的开销,当然在现代硬件速度很快的情况下,这个影响基本忽略不计。
C++没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄露、死锁等异常安全问题。这个需要使用RAII来处理资源的管理问题,学习成本比较高。
C++标准库的异常体系定义得不够好,导致大家各自定义自己的异常体系,非常的混乱。
异常尽量规范使用,否则后果不堪设想,随意抛异常,外层捕获的用户苦不堪言。所以异常规范有两点:
1️⃣抛出异常类型都继承自一个基类。
2️⃣函数是否抛异常、抛什么异常,都使用 func() throw();的方式规范化。
所以总体是利大于弊的
